#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <time.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/time.h>
#include <dirent.h>
#include <sys/sysmacros.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <poll.h>

#include <openssl/evp.h>
#include <openssl/pkcs12.h>
#include <openssl/x509.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/err.h>
#include <sys/capability.h>
#include <sys/prctl.h>

#include "../audit-gm/alarm.c"
#include "../../lib/gm_crypto.h"

#define LOG_PATH "/var/log/audit/audit.log"
#define IDX_PATH "/var/log/audit/audit.log.idx"
#define SEAL_PATH "/var/log/audit/audit.log.seal"

/* 证书/口令派生 */
#define P12_PATH "/etc/pki/audit/sm2.p12"
#define SALT_PATH "/etc/pki/audit/p12_salt"
#define PASS_PATH "/etc/pki/audit/p12_pass"
#define SM2_ID "1234567812345678"
#define SALT_BYTES 16
#define DERIVE_BYTES 32
#define DERIVE_ITERATIONS 200000

/* 每行写 seal（测试保持1，生产可按需调节） */
#define ALWAYS_REFRESH_SEAL 1

/* 插件自身日志 */
#define GMLOG_DIR "/var/log/audit-gm"
#define AUDIT_DIR "/var/log/audit"
#define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

/* 单例锁 */
#define RUN_LOCK "/var/run/audisp-gmseal.lock"

static FILE *g_log = NULL;
static int g_run_lock_fd = -1;

static void log_open(void)
{
    // if (mkdir(GMLOG_DIR, 0755) != 0 && errno != EEXIST) {
    //   fprintf(stderr, "[gmseal] mkdir %s failed: %s (errno=%d)\n",
    //             GMLOG_DIR, strerror(errno), errno);
    // }
    // g_log = fopen(GMLOG_FILE, "a");
    // if (!g_log) {
    //     fprintf(stderr, "[gmseal] open %s failed: %s; fallback to /tmp/audisp-gmseal.log\n",
    //             GMLOG_FILE, strerror(errno));
    //     g_log = fopen("/tmp/audisp-gmseal.log", "a");
    //     if (!g_log) {
    //         fprintf(stderr, "[gmseal] open /tmp/audisp-gmseal.log failed: %s; logging disabled\n",
    //                 strerror(errno));
    //     }
    // }
    g_log = NULL;
}


static void log_msg(const char *fmt, ...)
{
    // if (!g_log) return;
    // va_list ap;
    // va_start(ap, fmt);
    // time_t t = time(NULL);
    // char tb[64];
    // strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
    // fprintf(g_log, "[%s] ", tb);
    // vfprintf(g_log, fmt, ap);
    // fprintf(g_log, "\n");
    // fflush(g_log);
    // va_end(ap);
    (void)fmt; /* no-op: 禁用所有日志输出 */
}

/* ========== 运行单例 ========== */

static int acquire_singleton_lock(void)
{
    g_run_lock_fd = open(RUN_LOCK, O_CREAT|O_RDWR, 0644);
    if (g_run_lock_fd < 0) {
        log_msg("open run lock %s failed: %s", RUN_LOCK, strerror(errno));
        return -1;
    }
    if (flock(g_run_lock_fd, LOCK_EX | LOCK_NB) != 0) {
        log_msg("another gmseal instance is running (lock busy), exiting");
        return -1;
    }
    dprintf(g_run_lock_fd, "%ld\n", (long)getpid());
    return 0;
}
static void release_singleton_lock(void)
{
    if (g_run_lock_fd >= 0) {
        flock(g_run_lock_fd, LOCK_UN);
        close(g_run_lock_fd);
        unlink(RUN_LOCK);
        g_run_lock_fd = -1;
    }
}

/* ========== 通用小工具 ========== */

/* 最近 window_sec 秒内是否出现了新生成的 audit.log.N（提示 copytruncate） */
static int has_recent_rotated_copy(int window_sec)
{
    DIR *d = opendir(AUDIT_DIR);
    if (!d) return 0;

    time_t now = time(NULL);
    struct dirent *de;
    struct stat st;
    int found = 0;

    while ((de = readdir(d))) {
        if (strncmp(de->d_name, "audit.log.", 10) != 0) continue;
        const char *p = de->d_name + 10;
        if (!*p) continue;
        for (const char *q = p; *q; q++) {
            if (*q < '0' || *q > '9') { p = NULL; break; }
        }
        if (!p) continue;

        char cand[PATH_MAX];
        snprintf(cand, sizeof(cand), "%s/%s", AUDIT_DIR, de->d_name);
        if (stat(cand, &st) != 0) continue;
        if (S_ISREG(st.st_mode) && (now - st.st_mtime) <= window_sec) {
            found = 1;
            break;
        }
    }
    closedir(d);
    return found;
}


static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
{
    static const char hex[] = "0123456789abcdef";
    if (outlen < len * 2 + 1) return;
    for (size_t i = 0; i < len; i++) {
        out[i * 2] = hex[(buf[i] >> 4) & 0xF];
        out[i * 2 + 1] = hex[buf[i] & 0xF];
    }
    out[len * 2] = 0;
}

static char *b64_encode(const unsigned char *buf, size_t len)
{
    BIO *b64 = BIO_new(BIO_f_base64());
    BIO *mem = BIO_new(BIO_s_mem());
    if (!b64 || !mem) { if (b64) BIO_free(b64); if (mem) BIO_free(mem); return NULL; }
#ifdef BIO_FLAGS_BASE64_NO_NL
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
#endif
    BIO_push(b64, mem);
    if (BIO_write(b64, buf, (int)len) <= 0) { BIO_free_all(b64); return NULL; }
    (void)BIO_flush(b64);
    BUF_MEM *bptr = NULL;
    BIO_get_mem_ptr(b64, &bptr);
    char *out = malloc(bptr->length + 1);
    if (!out) { BIO_free_all(b64); return NULL; }
    memcpy(out, bptr->data, bptr->length);
    out[bptr->length] = 0;
    BIO_free_all(b64);
    return out;
}

static unsigned char *b64_decode(const char *b64, size_t *olen)
{
    BIO *b = BIO_new(BIO_f_base64());
    BIO *m = BIO_new_mem_buf(b64, -1);
    if (!b || !m) { if (b) BIO_free(b); if (m) BIO_free(m); return NULL; }
#ifdef BIO_FLAGS_BASE64_NO_NL
    BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
#endif
    BIO_push(b, m);
    size_t cap = strlen(b64) * 3 / 4 + 16;
    unsigned char *buf = malloc(cap);
    int n = BIO_read(b, buf, (int)cap);
    BIO_free_all(b);
    if (n <= 0) { free(buf); return NULL; }
    *olen = (size_t)n;
    return buf;
}

static int sm3_file_hex(const char *path, char out_hex[65])
{
    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3) return -1;
    FILE *f = fopen(path, "rb");
    if (!f) return -1;
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    if (!ctx) { fclose(f); return -1; }
    if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
    unsigned char buf[4096];
    size_t n;
    while ((n = fread(buf,1,sizeof(buf),f)) > 0) {
        if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
    }
    fclose(f);
    unsigned char md[32]; unsigned int mdlen = 0;
    if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
    EVP_MD_CTX_free(ctx);
    hexdump(md, 32, out_hex, 65);
    return 0;
}

static size_t normalize_line(char *buf, size_t len)
{
    while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) len--;
    return len;
}

static unsigned long long count_file_lines_simple(const char *path)
{
    FILE *f = fopen(path, "rb");
    if (!f) return 0;
    char *line = NULL; size_t cap = 0; ssize_t n; unsigned long long ln = 0;
    while ((n = getline(&line, &cap, f)) != -1) ln++;
    free(line); fclose(f); return ln;
}

/* ========== idx helpers ========== */
typedef struct {
    unsigned long long i;
    unsigned long long off;
    char lhash[65];
    char chash[65];
} IdxEntry;

typedef struct {
    IdxEntry *entries;
    size_t count;
} IdxArray;

static void free_idx_array(IdxArray *a) {
    if (!a) return;
    free(a->entries);
    a->entries = NULL; a->count = 0;
}

static int is_hex64_str(const char *s) {
    if (!s) return 0;
    size_t n = strlen(s);
    if (n != 64) return 0;
    for (size_t i = 0; i < 64; i++) {
        char c = s[i];
        if (!((c >= '0' && c <= '9') ||
              (c >= 'a' && c <= 'f') ||
              (c >= 'A' && c <= 'F'))) return 0;
    }
    return 1;
}

static int load_idx_entries(const char *path, IdxArray *out) {
    memset(out, 0, sizeof(*out));
    FILE *f = fopen(path, "r");
    if (!f) return -1;

    char buf[1024];
    IdxEntry *arr = NULL; size_t cnt = 0, capv = 0;

    while (fgets(buf, sizeof(buf), f)) {
        if (buf[0] == '#') continue;

        unsigned long long ii = 0, off = 0;
        char lhex[128] = {0}, chex[128] = {0};

        if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s",
                   &ii, &off, lhex, chex) == 4) {

            /* 必须是 64 字节十六进制；否则跳过该行 */
            if (!is_hex64_str(lhex) || !is_hex64_str(chex)) continue;

            if (cnt == capv) {
                size_t nc = capv ? capv * 2 : 256;
                IdxEntry *p = (IdxEntry *)realloc(arr, nc * sizeof(IdxEntry));
                if (!p) { free(arr); fclose(f); return -1; }
                arr = p; capv = nc;
            }

            /* 清零槽位，确保有 NUL */
            memset(&arr[cnt], 0, sizeof(arr[cnt]));
            arr[cnt].i = ii;
            arr[cnt].off = off;

            /* 只拷 64 个字节，并手动补 '\0' */
            memcpy(arr[cnt].lhash, lhex, 64);
            arr[cnt].lhash[64] = '\0';
            memcpy(arr[cnt].chash, chex, 64);
            arr[cnt].chash[64] = '\0';

            cnt++;
        }
    }

    fclose(f);
    out->entries = arr;
    out->count = cnt;
    return 0;
}

/* ---- 将现有 audit.tamperlines 重定位到“当前 audit.log”的实际行号/偏移”
 * 修正版：对相同 lhash 的多条记录做“有序一一分配”，避免重复绑定到同一行
 */

/* === 按 lhash 分组（为每个 lhash 做唯一分配） === */
    typedef struct {
        char lhash[65];

        /* 该 lhash 在 suspicious-lines 中的条目索引（旧顺序保持） */
        size_t *idxs; size_t idx_n, idx_cap;

        /* 在“当前 audit.log”中出现的位置（i, off, len），按 i 递增收集 */
        unsigned long long *gi, *goff, *glen;
        size_t g_n, g_cap;
    } LGroup;

    //2025-10.28
    //LGroup *groups = NULL; size_t g_n = 0, g_cap = 0;

    /* === 辅助函数：查找或新建分组 === */
static int find_or_add_group(LGroup **groups, size_t *g_n, size_t *g_cap, const char *h)
{
    for (size_t x = 0; x < *g_n; x++) {
        if (strcmp((*groups)[x].lhash, h) == 0)
            return (int)x;
    }

    if (*g_n == *g_cap) {
        size_t nc = *g_cap ? *g_cap * 2 : 32;
        LGroup *pp = (LGroup*)realloc(*groups, nc * sizeof(LGroup));
        if (!pp)
            return -1;
        *groups = pp;
        *g_cap  = nc;
    }

    memset(&(*groups)[*g_n], 0, sizeof(LGroup));
    strncpy((*groups)[*g_n].lhash, h, sizeof((*groups)[*g_n].lhash) - 1);
    return (int)((*g_n)++);
}


static int rebase_tamperlines_to_current_log(void)
{
    const char *TLP = "/var/log/audit/suspicious-lines";

    /* 打开并加锁 */
    // int tfd = open(TLP, O_RDWR | O_CREAT, 0600);
    // if (tfd < 0) return -1;
    int tfd = open(TLP, O_RDWR, 0);
    if (tfd < 0) {
        if (errno == ENOENT) return 0;  /* 没有 suspicious-lines 就啥也不做 */
        return -1;
    }
    if (flock(tfd, LOCK_EX) != 0) { close(tfd); return -1; }

    FILE *rf = fdopen(dup(tfd), "r");
    if (!rf) { flock(tfd, LOCK_UN); close(tfd); return -1; }

    //2025-10.28
    LGroup *groups = NULL; 
    size_t g_n = 0, g_cap = 0; 

    /* 解析 suspicious-lines 到内存 */
    typedef struct {
        unsigned long long old_i, old_off, old_len;
        unsigned long long new_i, new_off, new_len;
        ino_t   old_ino;
        int     found;
        char    ts[64];
        char    lhash[65];
        char    textpart[512]; /* 含前导空格的 ' text="..."' 片段（如有） */
    } TLEntry;

    TLEntry *arr = NULL; size_t cnt = 0, cap = 0;
    char *line = NULL; size_t capline = 0; ssize_t n;

    while ((n = getline(&line, &capline, rf)) != -1) {
        unsigned long long oi=0, oo=0, ol=0, oino_ull=0;
        char ts[64]={0}, lhex[65]={0};

        if (sscanf(line, "ts=%63s i=%llu off=%llu ino=%llu len=%llu lhash=%64s",
                   ts, &oi, &oo, &oino_ull, &ol, lhex) != 6) {
            continue;
        }

        char *tp = strstr(line, " text=");
        char textpart[512] = {0};
        if (tp) {
            size_t left = (size_t)(line + n - tp);
            if (left >= sizeof(textpart)) left = sizeof(textpart) - 1;
            memcpy(textpart, tp, left);
            textpart[left] = 0;
            size_t L = strlen(textpart);
            while (L && (textpart[L-1]=='\n'||textpart[L-1]=='\r')) textpart[--L]=0;
        }

        if (cnt == cap) {
            size_t nc = cap ? cap * 2 : 64;
            TLEntry *p = (TLEntry *)realloc(arr, nc * sizeof(*arr));
            if (!p) { free(arr); arr=NULL; cnt=cap=0; break; }
            arr = p; cap = nc;
        }
        memset(&arr[cnt], 0, sizeof(arr[cnt]));
        strncpy(arr[cnt].ts, ts, sizeof(arr[cnt].ts)-1);
        strncpy(arr[cnt].lhash, lhex, sizeof(arr[cnt].lhash)-1);
        strncpy(arr[cnt].textpart, textpart, sizeof(arr[cnt].textpart)-1);
        arr[cnt].old_i   = oi;
        arr[cnt].old_off = oo;
        arr[cnt].old_len = ol;
        arr[cnt].old_ino = (ino_t)oino_ull;
        cnt++;
    }
    free(line);
    fclose(rf);

    if (cnt == 0) {
        flock(tfd, LOCK_UN); close(tfd);
        return 0;
    }

    

    /* 把 suspicious-lines 的条目挂到对应分组（按出现顺序保留） */
    for (size_t k = 0; k < cnt; k++) {
        int gid = find_or_add_group(&groups, &g_n, &g_cap, arr[k].lhash);
        if (gid < 0) { flock(tfd, LOCK_UN); close(tfd); free(arr); free(groups); return -1; }
        LGroup *G = &groups[gid];
        if (G->idx_n == G->idx_cap) {
            size_t nc = G->idx_cap ? G->idx_cap * 2 : 8;
            size_t *pp = (size_t*)realloc(G->idxs, nc * sizeof(size_t));
            if (!pp) { flock(tfd, LOCK_UN); close(tfd); free(arr); free(groups); return -1; }
            G->idxs = pp; G->idx_cap = nc;
        }
        G->idxs[G->idx_n++] = k;
    }

    /* 扫描当前 audit.log，收集每个分组在新文件的全部命中位置（按 i 递增） */
    struct stat st_now;
    if (stat(LOG_PATH, &st_now) != 0) {
        flock(tfd, LOCK_UN); close(tfd); free(arr); free(groups); return -1;
    }
    ino_t cur_ino = st_now.st_ino;

    FILE *lf = fopen(LOG_PATH, "rb");
    if (!lf) { flock(tfd, LOCK_UN); close(tfd); free(arr); free(groups); return -1; }

    unsigned long long i = 0, off = 0;
    char *lbuf = NULL; size_t lcap = 0; ssize_t m;
    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3) { fclose(lf); flock(tfd, LOCK_UN); close(tfd); free(arr); free(groups); return -1; }

    while ((m = getline(&lbuf, &lcap, lf)) != -1) {
        unsigned long long off_before = off;
        i++;
        size_t norm = normalize_line(lbuf, (size_t)m);

        /* SM3(line) -> hhex */
        unsigned char md[32]; unsigned int mdlen=0;
        EVP_MD_CTX *ctx = EVP_MD_CTX_new();
        if (!ctx ||
            EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
            EVP_DigestUpdate(ctx, lbuf, norm) != 1 ||
            EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) {
            if (ctx) EVP_MD_CTX_free(ctx);
            free(lbuf); fclose(lf); flock(tfd, LOCK_UN); close(tfd); free(arr); free(groups);
            return -1;
        }
        EVP_MD_CTX_free(ctx);
        char hhex[65];
        hexdump(md, 32, hhex, sizeof(hhex));

        /* 找到同 lhash 的分组，加入候选位置 */
        for (size_t gx = 0; gx < g_n; gx++) {
            if (strcmp(groups[gx].lhash, hhex) == 0) {
                LGroup *G = &groups[gx];
                if (G->g_n == G->g_cap) {
                    size_t nc = G->g_cap ? G->g_cap * 2 : 8;
                    unsigned long long *pi = (unsigned long long*)realloc(G->gi,   nc * sizeof(unsigned long long));
                    unsigned long long *po = (unsigned long long*)realloc(G->goff, nc * sizeof(unsigned long long));
                    unsigned long long *pl = (unsigned long long*)realloc(G->glen, nc * sizeof(unsigned long long));
                    if (!pi || !po || !pl) {
                        free(pi); free(po); free(pl);
                        free(lbuf); fclose(lf); flock(tfd, LOCK_UN); close(tfd);
                        /* 释放 groups 内已分配成员 */
                        for (size_t k2=0;k2<g_n;k2++){ free(groups[k2].idxs); free(groups[k2].gi); free(groups[k2].goff); free(groups[k2].glen); }
                        free(groups); free(arr);
                        return -1;
                    }
                    G->gi = pi; G->goff = po; G->glen = pl; G->g_cap = nc;
                }
                G->gi[G->g_n]   = i;
                G->goff[G->g_n] = off_before;
                G->glen[G->g_n] = norm;
                G->g_n++;
                break; /* 一个分组命中即可 */
            }
        }

        off += (unsigned long long)m;
    }
    free(lbuf);
    fclose(lf);

    /* === 分配：对每个分组，按出现顺序一一对应（避免重复占用） ===
       - G->idxs[] 是原文件中的条目顺序（基本等同 old_i 递增）
       - G->gi[]   是当前文件中的实际位置（递增）
       - 逐一配对，超出部分记 deleted
    */
    for (size_t gx = 0; gx < g_n; gx++) {
        LGroup *G = &groups[gx];

        /* 为了稳妥，按 old_i 对 idxs 做一次排序（多数场景本来就是递增） */
        if (G->idx_n > 1) {
            /* 简单的插入排序，避免引入 qsort 回调捕外部数据 */
            for (size_t a = 1; a < G->idx_n; a++) {
                size_t key = G->idxs[a];
                long long j = (long long)a - 1;
                while (j >= 0 && arr[G->idxs[j]].old_i > arr[key].old_i) {
                    G->idxs[j+1] = G->idxs[j];
                    j--;
                }
                G->idxs[j+1] = key;
            }
        }

        size_t pairs = (G->idx_n < G->g_n) ? G->idx_n : G->g_n;
        for (size_t t = 0; t < pairs; t++) {
            size_t ai = G->idxs[t];
            arr[ai].new_i   = G->gi[t];
            arr[ai].new_off = G->goff[t];
            arr[ai].new_len = G->glen[t];
            arr[ai].found   = 1;
        }
        /* 剩余没分到位置的，保持 found=0 -> 后面写 status=deleted */
    }

    /* 写回（覆盖） suspicious-lines */
    char tmp_path[PATH_MAX];
    snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", TLP);
    int wfd = open(tmp_path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
    if (wfd < 0) {
        flock(tfd, LOCK_UN); close(tfd);
        for (size_t k2=0;k2<g_n;k2++){ free(groups[k2].idxs); free(groups[k2].gi); free(groups[k2].goff); free(groups[k2].glen); }
        free(groups); free(arr);
        return -1;
    }

    time_t now = time(NULL);
    struct tm tmv; localtime_r(&now, &tmv);
    char rts[32]; strftime(rts, sizeof(rts), "%Y-%m-%dT%H:%M:%S", &tmv);

    for (size_t k = 0; k < cnt; k++) {
        if (arr[k].found) {
            dprintf(wfd,
                    "ts=%s i=%llu off=%llu ino=%ju len=%llu lhash=%s rebased=1 rts=%s%s\n",
                    arr[k].ts,
                    (unsigned long long)arr[k].new_i,
                    (unsigned long long)arr[k].new_off,
                    (uintmax_t)cur_ino,
                    (unsigned long long)arr[k].new_len,
                    arr[k].lhash,
                    rts,
                    arr[k].textpart[0] ? arr[k].textpart : "");
        } else {
            dprintf(wfd,
                    "ts=%s i=%llu off=%llu ino=%ju len=%llu lhash=%s status=deleted rts=%s%s\n",
                    arr[k].ts,
                    (unsigned long long)arr[k].old_i,
                    (unsigned long long)arr[k].old_off,
                    (uintmax_t)arr[k].old_ino,
                    (unsigned long long)arr[k].old_len,
                    arr[k].lhash,
                    rts,
                    arr[k].textpart[0] ? arr[k].textpart : "");
        }
    }

    fsync(wfd);
    close(wfd);
    rename(tmp_path, TLP);
    fsync(tfd);
    flock(tfd, LOCK_UN);
    close(tfd);

    for (size_t k2=0;k2<g_n;k2++){ free(groups[k2].idxs); free(groups[k2].gi); free(groups[k2].goff); free(groups[k2].glen); }
    free(groups);
    free(arr);
    return 0;
}



/* -------- seal 解析 -------- */
struct seal {
    char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
};

static void trimnl(char *s) { size_t n = strlen(s); while (n && (s[n-1]=='\n'||s[n-1]=='\r')) s[--n] = 0; }
static char *trimws(char *s){ while(*s==' '||*s=='\t') s++; size_t n=strlen(s); while(n>0&&(s[n-1]==' '||s[n-1]=='\t'||s[n-1]=='\r'||s[n-1]=='\n')) s[--n]=0; return s;}

static int parse_seal_file(const char *seal_path, struct seal *S)
{
    memset(S,0,sizeof(*S));
    FILE *f = fopen(seal_path,"r");
    if (!f) return -1;
    char *line = NULL; size_t cap=0; ssize_t n;
    while ((n = getline(&line,&cap,f)) != -1) {
        if (line[0]=='#' || line[0]==0) continue;
        trimnl(line);
        char *eq = strchr(line,'=');
        if (!eq) continue;
        *eq = 0;
        char *k = trimws(line);
        char *v = trimws(eq+1);
        if (strcmp(k,"version")==0) S->version=strdup(v);
        else if (strcmp(k,"hash")==0) S->hash=strdup(v);
        else if (strcmp(k,"sign")==0) S->sign=strdup(v);
        else if (strcmp(k,"sm2_id")==0) S->sm2_id=strdup(v);
        else if (strcmp(k,"path")==0) S->path=strdup(v);
        else if (strcmp(k,"records")==0) S->records=strdup(v);
        else if (strcmp(k,"chain")==0) S->chain=strdup(v);
        else if (strcmp(k,"key_fpr")==0) S->key_fpr=strdup(v);
        else if (strcmp(k,"cert_der_base64")==0) S->cert_b64=strdup(v);
        else if (strcmp(k,"sig_base64")==0) S->sig_b64=strdup(v);
        else if (strcmp(k,"index_sm3")==0) S->index_sm3=strdup(v);
    }
    free(line); fclose(f);
    if (S->version && S->hash && S->sign && S->sm2_id && S->path &&
        S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3) return 0;
    return -1;
}
static void free_seal(struct seal *S)
{
    free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
    free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
    free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
}
static char *build_canon_from_seal(struct seal *S)
{
    char *buf = NULL;
    if (asprintf(&buf,
        "version=%s\n"
        "hash=%s\n"
        "sign=%s\n"
        "sm2_id=%s\n"
        "path=%s\n"
        "records=%s\n"
        "chain=%s\n"
        "key_fpr=%s\n"
        "index_sm3=%s\n",
        S->version,S->hash,S->sign,S->sm2_id,S->path,S->records,S->chain,S->key_fpr,S->index_sm3) < 0) return NULL;
    return buf;
}

/* ---------- 简易 SM3 链（限制行数） ---------- */
static int sm3_chain_file_limit_local(const char *path, unsigned long long limit_lines, unsigned char out32[32])
{
    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3) return -1;
    unsigned char H[32] = {0};
    unsigned long long scanned = 0;
    FILE *fp = fopen(path,"rb");
    if (!fp) return -1;
    char *line = NULL; size_t cap=0; ssize_t n;
    while (scanned < limit_lines && (n = getline(&line,&cap,fp)) != -1) {
        size_t normlen = normalize_line(line,(size_t)n);
        EVP_MD_CTX *ctx = EVP_MD_CTX_new();
        if (!ctx) { fclose(fp); free(line); return -1; }
        int ok = (EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
                  EVP_DigestUpdate(ctx, H, sizeof(H)) == 1 &&
                  EVP_DigestUpdate(ctx, line, normlen) == 1);
        unsigned int mdlen = 0;
        if (!ok || EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
        EVP_MD_CTX_free(ctx);
        scanned++;
    }
    free(line); fclose(fp); memcpy(out32,H,32); return 0;
}

/* ---------- idx handle ---------- */
static int g_idx_fd = -1;
static FILE *g_idx = NULL;

static void close_idx_handle(void) {
    if (g_idx) { fclose(g_idx); g_idx = NULL; }
    if (g_idx_fd >= 0) { close(g_idx_fd); g_idx_fd = -1; }
}
static int open_idx_handle(void) {
    if (g_idx_fd >= 0) return 0;
    int fd = open(IDX_PATH, O_RDWR | O_CREAT | O_APPEND, 0600);
    if (fd < 0) {
        log_msg("open_idx_handle: open %s failed: %s", IDX_PATH, strerror(errno));
        return -1;
    }
    FILE *f = fdopen(dup(fd), "a+");
    if (!f) { log_msg("open_idx_handle: fdopen failed: %s", strerror(errno)); close(fd); return -1; }
    g_idx_fd = fd;
    g_idx = f;
    fchmod(g_idx_fd, 0600);
    return 0;
}

static int g_degraded_mode = 0;

static ino_t s_last_trunc_ino = 0;
static unsigned long long s_last_trunc_oldoff = 0;
static time_t s_last_trunc_ts = 0;

static int append_idx_line_atomic(unsigned long long i, unsigned long long off, const char *lhex, const char *chex) {
    if (g_degraded_mode) {
        log_msg("append_idx_line_atomic: degraded mode, skip append");
        return 0;
    }
    if (g_idx_fd < 0) {
        log_msg("append_idx_line_atomic: idx fd not open");
        return -1;
    }
    if (flock(g_idx_fd, LOCK_EX) != 0) {
        log_msg("append_idx_line_atomic: flock EX failed: %s", strerror(errno));
    }
    int ret = 0;
    ssize_t w = dprintf(g_idx_fd, "i=%llu off=%llu lhash=%s chash=%s\n",
                        (unsigned long long)i, (unsigned long long)off, lhex, chex);
    if (w < 0) { log_msg("append_idx_line_atomic: dprintf failed: %s", strerror(errno)); ret = -1; }
    else fsync(g_idx_fd);
    flock(g_idx_fd, LOCK_UN);
    return ret;
}

/* 读取“指定路径”的 idx 尾项 */
static int read_last_idx_entry_from_path(const char *idxpath, IdxEntry *out)
{
    if (!idxpath || !out) return -1;
    memset(out, 0, sizeof(*out));
    FILE *ix = fopen(idxpath, "r");
    if (!ix) return -1;

    char buf[1024];
    int have = 0;
    while (fgets(buf, sizeof(buf), ix)) {
        if (buf[0] == '#') continue;
        unsigned long long i = 0, off = 0;
        char lhex[128] = {0}, chex[128] = {0};
        if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &i, &off, lhex, chex) == 4) {
            if (!is_hex64_str(lhex) || !is_hex64_str(chex)) continue;
            out->i = i; out->off = off;
            memcpy(out->lhash, lhex, 64); out->lhash[64] = '\0';
            memcpy(out->chash, chex, 64); out->chash[64] = '\0';
            have = 1;
        }
    }
    fclose(ix);
    return have ? 0 : -1;
}


// 新增：按“文件自身内容”重建 idx（limit_lines==0 表示全量）
// 计算每行 lhash（SM3(line)）和滚动 chash（SM3(prevH || line)），写入 idx，返回链值 H。
static int rebuild_idx_from_file_limit(const char *logpath,
                                       const char *idxpath,
                                       unsigned long long limit_lines,
                                       unsigned char out_chain[32])
{
    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3) return -1;

    FILE *lf = fopen(logpath, "rb");
    if (!lf) return -1;

    char *line = NULL; size_t cap = 0; ssize_t n;
    unsigned long long ln = 0, offset = 0;
    unsigned char H[32]; memset(H, 0, sizeof(H));

    char tmppath[PATH_MAX];
    snprintf(tmppath, sizeof(tmppath), "%s.tmp", idxpath);
    FILE *tf = fopen(tmppath, "w");
    if (!tf) { fclose(lf); return -1; }
    chmod(tmppath, 0600);

    while ((limit_lines == 0 || ln < limit_lines) && (n = getline(&line, &cap, lf)) != -1) {
        ln++;
        size_t normlen = normalize_line(line, (size_t)n);

        // lhash = SM3(line)
        unsigned char L[32]; unsigned int lm = 0;
        EVP_MD_CTX *c1 = EVP_MD_CTX_new();
        if (!c1 ||
            EVP_DigestInit_ex(c1, sm3, NULL) != 1 ||
            EVP_DigestUpdate(c1, line, normlen) != 1 ||
            EVP_DigestFinal_ex(c1, L, &lm) != 1 || lm != 32) {
            if (c1) EVP_MD_CTX_free(c1);
            fclose(lf); fclose(tf); unlink(tmppath); free(line);
            return -1;
        }
        EVP_MD_CTX_free(c1);

        // H = SM3(H || line)
        unsigned char newH[32]; unsigned int hm = 0;
        EVP_MD_CTX *c2 = EVP_MD_CTX_new();
        if (!c2 ||
            EVP_DigestInit_ex(c2, sm3, NULL) != 1 ||
            EVP_DigestUpdate(c2, H, sizeof(H)) != 1 ||
            EVP_DigestUpdate(c2, line, normlen) != 1 ||
            EVP_DigestFinal_ex(c2, newH, &hm) != 1 || hm != 32) {
            if (c2) EVP_MD_CTX_free(c2);
            fclose(lf); fclose(tf); unlink(tmppath); free(line);
            return -1;
        }
        EVP_MD_CTX_free(c2);
        memcpy(H, newH, 32);

        char lhex[65], chex[65];
        hexdump(L, 32, lhex, sizeof(lhex));
        hexdump(H, 32, chex, sizeof(chex));
        if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n",
                    (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0) {
            fclose(lf); fclose(tf); unlink(tmppath); free(line);
            return -1;
        }
        offset += (unsigned long long)n;
    }

    free(line);
    fclose(lf);
    fflush(tf); fsync(fileno(tf)); fclose(tf);

    if (rename(tmppath, idxpath) != 0) { unlink(tmppath); return -1; }
    chmod(idxpath, 0600);

    if (out_chain) memcpy(out_chain, H, 32);
    return 0;
}



/* *** 全量重建：仅在 idx 为空时使用 *** */
static int rebuild_idx_from_log_atomic(const char *logpath, const char *idxpath,
                                       unsigned char out_chain[32],
                                       unsigned long long *out_lines,
                                       unsigned long long *out_offset)
{
    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3) return -1;
    FILE *lf = fopen(logpath,"rb");
    if (!lf) return -1;
    char *line = NULL; size_t cap=0; ssize_t n;
    unsigned long long ln=0, offset=0;
    unsigned char H[32]; memset(H,0,sizeof(H));

    close_idx_handle();

    char tmppath[PATH_MAX];
    snprintf(tmppath,sizeof(tmppath), "%s.tmp", idxpath);
    FILE *tf = fopen(tmppath, "w");
    if (!tf) { fclose(lf); return -1; }
    chmod(tmppath, 0600);

    while ((n = getline(&line,&cap,lf)) != -1) {
        ln++;
        size_t normlen = normalize_line(line,(size_t)n);
        EVP_MD_CTX *c1 = EVP_MD_CTX_new(); unsigned char L[32]; unsigned int lm=0;
        if (!c1 || EVP_DigestInit_ex(c1, sm3, NULL)!=1 || EVP_DigestUpdate(c1, line, normlen)!=1 || EVP_DigestFinal_ex(c1,L,&lm)!=1 || lm!=32) {
            if (c1) EVP_MD_CTX_free(c1);
            fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
        }
        EVP_MD_CTX_free(c1);

        EVP_MD_CTX *c2 = EVP_MD_CTX_new(); unsigned char newH[32]; unsigned int hm=0;
        if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL)!=1 || EVP_DigestUpdate(c2, H, sizeof(H))!=1 || EVP_DigestUpdate(c2, line, normlen)!=1 || EVP_DigestFinal_ex(c2, newH, &hm)!=1 || hm!=32) {
            if (c2) EVP_MD_CTX_free(c2);
            fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
        }
        EVP_MD_CTX_free(c2);
        memcpy(H, newH, 32);

        char lhex[65], chex[65];
        hexdump(L,32,lhex,sizeof(lhex));
        hexdump(H,32,chex,sizeof(chex));
        if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n",
                    (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0) {
            fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
        }
        offset += (unsigned long long)n;
    }
    free(line); fclose(lf);
    fflush(tf); fsync(fileno(tf)); fclose(tf);
    if (rename(tmppath, idxpath) != 0) { unlink(tmppath); return -1; }
    chmod(idxpath, 0600);
    if (open_idx_handle() != 0) { log_msg("rebuild: open_idx_handle failed after rename"); return -1; }
    if (out_chain) memcpy(out_chain,H,32);
    if (out_lines) *out_lines = ln;
    if (out_offset) *out_offset = offset;
    return 0;
}

static int truncate_idx_to_i_atomic(const char *idxpath, unsigned long long keep_i)
{
    if (g_degraded_mode) {
        log_msg("truncate_idx_to_i_atomic: degraded mode, skip truncation");
        return 0;
    }
    close_idx_handle();
    FILE *in = fopen(idxpath,"r");
    if (!in) {
        if (keep_i == 0) {
            FILE *out = fopen(idxpath,"w");
            if (!out) return -1;
            fclose(out);
            chmod(idxpath, 0600);
            if (open_idx_handle() != 0) return -1;
            return 0;
        }
        return -1;
    }
    char tmp[PATH_MAX];
    snprintf(tmp,sizeof(tmp), "%s.trunc", idxpath);
    FILE *out = fopen(tmp, "w");
    if (!out) { fclose(in); return -1; }
    char buf[2048];
    while (fgets(buf,sizeof(buf),in)) {
        if (buf[0]=='#') { if (fputs(buf,out) < 0) { fclose(in); fclose(out); unlink(tmp); return -1; } continue; }
        unsigned long long i=0, off=0; char lhex[256]={0}, chex[256]={0};
        if (sscanf(buf,"i=%llu off=%llu lhash=%255s chash=%255s",&i,&off,lhex,chex)==4) {
            if (i <= keep_i) {
                if (fprintf(out,"i=%llu off=%llu lhash=%s chash=%s\n",i,off,lhex,chex) < 0) { fclose(in); fclose(out); unlink(tmp); return -1; }
            } else break;
        }
    }
    fclose(in);
    fflush(out); fsync(fileno(out)); fclose(out);
    if (rename(tmp, idxpath) != 0) { unlink(tmp); return -1; }
    chmod(idxpath, 0600);
    if (open_idx_handle() != 0) { log_msg("truncate: open_idx_handle failed after rename"); return -1; }
    return 0;
}

/* ---------- 全局状态 ---------- */
static unsigned char g_chain[32];
static unsigned long long g_lines = 0;
static unsigned long long g_offset = 0;
static int g_run = 1;

/* 持有当前 log 的 FD/FP 与 inode（用于轮转/可疑重写检测） */
static int   g_log_fd  = -1;
static FILE *g_log_fp  = NULL;
static dev_t g_log_dev = 0;
static ino_t g_log_ino = 0;

static void on_signal(int sig) { if (sig == SIGINT || sig == SIGTERM) g_run = 0; }

static void enter_degraded_mode(const char *why)
{
    if (!g_degraded_mode) {
        g_degraded_mode = 1;
        log_msg("ENTER DEGRADED MODE: %s", why ? why : "unknown");
        // trigger_alarm("gmseal degraded mode: %s", why ? why : "unknown");
    }
}

/* 打开/关闭当前 log 句柄 */
static int open_log_handle(void)
{
    if (g_log_fd >= 0) return 0;
    g_log_fd = open(LOG_PATH, O_RDONLY | O_CLOEXEC);
    if (g_log_fd < 0) {
        log_msg("open_log_handle: open %s failed: %s", LOG_PATH, strerror(errno));
        return -1;
    }
    struct stat st;
    if (fstat(g_log_fd, &st) != 0) {
        log_msg("open_log_handle: fstat failed: %s", strerror(errno));
        close(g_log_fd); g_log_fd = -1;
        return -1;
    }
    g_log_dev = st.st_dev;
    g_log_ino = st.st_ino;
    g_log_fp = fdopen(dup(g_log_fd), "rb");
    if (!g_log_fp) {
        log_msg("open_log_handle: fdopen failed: %s", strerror(errno));
        close(g_log_fd); g_log_fd = -1;
        return -1;
    }
    return 0;
}
static void close_log_handle(void)
{
    if (g_log_fp) { fclose(g_log_fp); g_log_fp = NULL; }
    if (g_log_fd >= 0) { close(g_log_fd); g_log_fd = -1; }
    g_log_dev = 0; g_log_ino = 0;
}

/* 从 idx 尾恢复 */
static int recover_tail_from_idx(unsigned long long *out_i,
                                 unsigned long long *out_off,
                                 unsigned char out_chain[32])
{
    FILE *ix = fopen(IDX_PATH, "r");
    if (!ix) return -1;
    char buf[1024]; unsigned long long last_i = 0, last_off = 0;
    char last_ch[65] = {0};
    while (fgets(buf, sizeof(buf), ix)) {
        if (buf[0]=='#') continue;
        unsigned long long i=0, off=0; char l[128]={0}, c[128]={0};
        if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,l,c)==4) {
            last_i = i; last_off = off; strncpy(last_ch, c, sizeof(last_ch)-1);
        }
    }
    fclose(ix);
    if (out_i) *out_i = last_i;
    if (out_off) *out_off = last_off;
    if (out_chain) {
        if (last_i == 0) { memset(out_chain,0,32); return 0; }
        for (int i=0;i<32;i++){
            unsigned int hi,lo;
            if (sscanf(&last_ch[i*2], "%1x%1x", &hi,&lo) != 2) return -1;
            out_chain[i]=(hi<<4)|lo;
        }
    }
    return 0;
}

static int read_last_idx_entry(IdxEntry *out)
{
    if (!out) return -1;
    memset(out, 0, sizeof(*out));
    FILE *ix = fopen(IDX_PATH, "r");
    if (!ix) return -1;

    char buf[1024];
    int have = 0;
    while (fgets(buf, sizeof(buf), ix)) {
        if (buf[0] == '#') continue;
        unsigned long long i = 0, off = 0;
        char lhex[128] = {0}, chex[128] = {0};
        if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &i, &off, lhex, chex) == 4) {
            if (!is_hex64_str(lhex) || !is_hex64_str(chex)) continue;
            out->i = i; out->off = off;
            memcpy(out->lhash, lhex, 64); out->lhash[64] = '\0';
            memcpy(out->chash, chex, 64); out->chash[64] = '\0';
            have = 1;
        }
    }
    fclose(ix);
    return have ? 0 : -1;
}


static unsigned long long idx_count_lines(void)
{
    FILE *f = fopen(IDX_PATH, "r");
    if (!f) return 0;
    char buf[256];
    unsigned long long cnt = 0;
    while (fgets(buf,sizeof(buf),f)) {
        if (buf[0] == '#') continue;
        unsigned long long i=0, off=0; char l[128], c[128];
        if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,l,c)==4) cnt++;
    }
    fclose(f);
    return cnt;
}

/* 读取旧 FD 到 EOF，刷入 idx */
static int drain_old_file_to_eof(void)
{
    if (!g_log_fp) return 0;
    if (fseeko(g_log_fp, (off_t)g_offset, SEEK_SET) != 0) {
        log_msg("drain_old_file_to_eof: fseeko failed: %s", strerror(errno));
        return -1;
    }
    char *line = NULL; size_t cap = 0; ssize_t n;
    int cnt = 0;
    while ((n = getline(&line, &cap, g_log_fp)) > 0) {
        const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
        if (!sm3) { free(line); return -1; }
        size_t normlen = normalize_line(line,(size_t)n);

        unsigned char lhash[32], cand_chain[32];
        unsigned int lm=0, hm=0;

        EVP_MD_CTX *c1 = EVP_MD_CTX_new();
        if (!c1) { free(line); return -1; }
        if (EVP_DigestInit_ex(c1, sm3, NULL)!=1 ||
            EVP_DigestUpdate(c1, line, normlen)!=1 ||
            EVP_DigestFinal_ex(c1, lhash, &lm)!=1 || lm!=32) {
            EVP_MD_CTX_free(c1); free(line); return -1;
        }
        EVP_MD_CTX_free(c1);

        EVP_MD_CTX *c2 = EVP_MD_CTX_new();
        if (!c2) { free(line); return -1; }
        if (EVP_DigestInit_ex(c2, sm3, NULL)!=1 ||
            EVP_DigestUpdate(c2, g_chain, sizeof(g_chain))!=1 ||
            EVP_DigestUpdate(c2, line, normlen)!=1 ||
            EVP_DigestFinal_ex(c2, cand_chain, &hm)!=1 || hm!=32) {
            EVP_MD_CTX_free(c2); free(line); return -1;
        }
        EVP_MD_CTX_free(c2);

        char lhex[65], chex[65];
        hexdump(lhash,32,lhex,sizeof(lhex));
        hexdump(cand_chain,32,chex,sizeof(chex));

        if (g_idx_fd < 0 && open_idx_handle() != 0) {
            log_msg("drain_old_file_to_eof: open_idx_handle failed");
            free(line); return -1;
        }
        if (append_idx_line_atomic(g_lines + 1, g_offset, lhex, chex) != 0) {
            log_msg("drain_old_file_to_eof: append idx failed");
            free(line); return -1;
        }
        memcpy(g_chain, cand_chain, 32);
        g_lines++;
        g_offset += (unsigned long long)n;
        cnt++;
    }
    free(line);
    return cnt;
}

/* ---- 写 seal（通用） ---- */
static int gmseal_write_seal_for(const char *log_path_for_canon,
                                 const char *idx_path_for_hash,
                                 const char *seal_out_path)
{
    if (g_degraded_mode) {
        log_msg("In degraded mode: skip writing seal_for(%s)", log_path_for_canon);
        return 0;
    }

    int rc = -1;
    EVP_PKEY *pkey = NULL;
    X509 *cert = NULL;
    char *pass_in = NULL;

    /* --- 读取 PASS_PATH --- */
    {
        FILE *pf = fopen(PASS_PATH,"r");
        if (!pf) { log_msg("open PASS_PATH failed"); return -1; }
        size_t cap = 0; ssize_t n = getline(&pass_in, &cap, pf);
        fclose(pf);
        if (n <= 0) { free(pass_in); log_msg("read PASS_PATH empty"); return -1; }
        while (n && (pass_in[n-1]=='\n'||pass_in[n-1]=='\r')) pass_in[--n]=0;
    }

    /* --- 读取 salt（十六进制） --- */
    unsigned char salt[SALT_BYTES];
    {
        FILE *sf = fopen(SALT_PATH,"r");
        if (!sf) { log_msg("open SALT_PATH failed"); free(pass_in); return -1; }
        char saltbuf[256];
        if (!fgets(saltbuf,sizeof(saltbuf),sf)) { fclose(sf); free(pass_in); return -1; }
        fclose(sf);
        size_t slen = strlen(saltbuf);
        while (slen && (saltbuf[slen-1]=='\n'||saltbuf[slen-1]=='\r')) saltbuf[--slen]=0;
        if (slen != (size_t)SALT_BYTES*2) { log_msg("salt length error"); free(pass_in); return -1; }
        for (size_t i=0;i<SALT_BYTES;i++){
            unsigned int hi,lo;
            if (sscanf(&saltbuf[i*2], "%1x%1x", &hi,&lo) != 2) { free(pass_in); return -1; }
            salt[i] = (hi<<4)|lo;
        }
    }

    /* --- 判断 pass_in 是否 64 个十六进制字符 --- */
    int is_hex64 = (strlen(pass_in)==64);
    if (is_hex64) {
        for (int i=0;i<64;i++) {
            char c = pass_in[i];
            if (!((c>='0'&&c<='9')||(c>='a'&&c<='f'))) { is_hex64 = 0; break; }
        }
    }

    /* --- 得到用于 PKCS12_parse 的口令 passhex --- */
    char passhex[DERIVE_BYTES*2+1] = {0};
    int derived_from_raw = 0;

    if (is_hex64) {
        strncpy(passhex, pass_in, sizeof(passhex)-1);
    } else {
        const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
        if (!sm3) { log_msg("no sm3"); OPENSSL_cleanse(pass_in, strlen(pass_in)); free(pass_in); return -1; }
        unsigned char derived[DERIVE_BYTES];
        if (!PKCS5_PBKDF2_HMAC(pass_in, strlen(pass_in), salt, SALT_BYTES,
                               DERIVE_ITERATIONS, sm3, DERIVE_BYTES, derived)) {
            log_msg("PBKDF2-SM3 failed");
            OPENSSL_cleanse(pass_in, strlen(pass_in)); free(pass_in);
            return -1;
        }
        hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
        derived_from_raw = 1;
    }

    /* --- 打开 P12 并解析 --- */
    {
        FILE *pf2 = fopen(P12_PATH,"rb");
        if (!pf2) { log_msg("open P12_PATH failed"); OPENSSL_cleanse(pass_in, strlen(pass_in)); free(pass_in); return -1; }
        PKCS12 *p12 = d2i_PKCS12_fp(pf2, NULL); fclose(pf2);
        if (!p12) { log_msg("d2i_PKCS12_fp failed"); OPENSSL_cleanse(pass_in, strlen(pass_in)); free(pass_in); return -1; }

        if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL)) {
            if (!is_hex64 && derived_from_raw) {
                log_msg("PKCS12_parse failed with derived hex, retry with PASS_PATH raw");
                if (!PKCS12_parse(p12, pass_in, &pkey, &cert, NULL)) {
                    log_msg("PKCS12_parse failed with PASS_PATH raw as well");
                    PKCS12_free(p12);
                    OPENSSL_cleanse(pass_in, strlen(pass_in)); free(pass_in);
                    return -1;
                }
            } else {
                log_msg("PKCS12_parse failed");
                PKCS12_free(p12);
                OPENSSL_cleanse(pass_in, strlen(pass_in)); free(pass_in);
                return -1;
            }
        }
        PKCS12_free(p12);
    }

    OPENSSL_cleanse(pass_in, strlen(pass_in));
    free(pass_in);

    /* --- 证书指纹（SM3 over DER） --- */
    unsigned char *der=NULL; int derlen = i2d_X509(cert, &der);
    if (derlen <= 0) { EVP_PKEY_free(pkey); X509_free(cert); return -1; }
    EVP_MD_CTX *ctx = EVP_MD_CTX_new(); unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
    if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
        EVP_DigestUpdate(ctx, der, derlen) != 1 || EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1) {
        if (ctx) EVP_MD_CTX_free(ctx); OPENSSL_free(der); EVP_PKEY_free(pkey); X509_free(cert); return -1;
    }
    EVP_MD_CTX_free(ctx); OPENSSL_free(der);
    char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));

    /* --- 组装 canon 与签名 --- */
    char chain_hex[65]; hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));

    char idx_sm3[65] = {0};
    if (sm3_file_hex(idx_path_for_hash, idx_sm3) != 0) {
        EVP_PKEY_free(pkey); X509_free(cert);
        log_msg("sm3 idx failed for %s", idx_path_for_hash);
        return -1;
    }

    char *canon = NULL;
    if (asprintf(&canon,
        "version=1\nhash=sm3\nsign=sm2\nsm2_id=%s\npath=%s\nrecords=%llu\nchain=%s\nkey_fpr=%s\nindex_sm3=%s\n",
        SM2_ID, log_path_for_canon, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0) {
        EVP_PKEY_free(pkey); X509_free(cert); return -1;
    }

    unsigned char *sig=NULL; size_t siglen = 0;
    if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon), SM2_ID, &sig, &siglen) != 0) {
        log_msg("gm_sm2_sign failed"); free(canon); EVP_PKEY_free(pkey); X509_free(cert); return -1;
    }
    char *sigb64 = b64_encode(sig, siglen);
    OPENSSL_free(sig);

    unsigned char *der2=NULL; int der2len = i2d_X509(cert, &der2);
    char *certb64 = NULL;
    if (der2len > 0) { certb64 = b64_encode(der2, der2len); OPENSSL_free(der2); }
    else { log_msg("i2d_X509 failed"); free(canon); free(sigb64); EVP_PKEY_free(pkey); X509_free(cert); return -1; }

    EVP_PKEY_free(pkey); X509_free(cert);

    /* --- 落盘 .seal --- */
    char tmp[PATH_MAX]; snprintf(tmp,sizeof(tmp), "%s.tmp", seal_out_path);
    FILE *sfw = fopen(tmp, "w");
    if (!sfw) { log_msg("open seal tmp failed: %s", strerror(errno)); free(canon); free(sigb64); free(certb64); return -1; }
    fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n%s", canon);
    fprintf(sfw, "cert_der_base64=%s\n", certb64);
    fprintf(sfw, "sig_base64=%s\n", sigb64);
    fclose(sfw);
    chmod(tmp, 0600);
    if (rename(tmp, seal_out_path) != 0) {
        log_msg("rename seal tmp -> %s failed: %s", seal_out_path, strerror(errno));
        unlink(tmp); free(canon); free(sigb64); free(certb64); return -1;
    }
    free(canon); free(sigb64); free(certb64);

    log_msg("seal written: %s (records=%llu path=%s)", seal_out_path, (unsigned long long)g_lines, log_path_for_canon);
    rc = 0;
    return rc;
}

/* ---------- 文件拷贝/快照 ---------- */

static int copy_file_path_to_path(const char *src, const char *dst)
{
    int s = open(src, O_RDONLY);
    if (s < 0) return -1;
    int d = open(dst, O_WRONLY|O_CREAT|O_TRUNC, 0600);
    if (d < 0) { close(s); return -1; }
    char buf[1<<16];
    for (;;) {
        ssize_t n = read(s, buf, sizeof(buf));
        if (n == 0) break;
        if (n < 0) { if (errno==EINTR) continue; close(s); close(d); return -1; }
        char *p = buf; ssize_t m = n;
        while (m > 0) {
            ssize_t w = write(d, p, m);
            if (w < 0) { if (errno==EINTR) continue; close(s); close(d); return -1; }
            p += w; m -= w;
        }
    }
    fsync(d);
    (void)fchmod(d, 0400); //10-27
    close(s); close(d);
    return 0;
}

static int snapshot_current_fd_to_path(int fd, const char *dst)
{
    int d = open(dst, O_WRONLY|O_CREAT|O_TRUNC, 0600);
    if (d < 0) return -1;
    if (lseek(fd, 0, SEEK_SET) < 0) { close(d); return -1; }
    char buf[1<<16];
    for (;;) {
        ssize_t n = read(fd, buf, sizeof(buf));
        if (n == 0) break;
        if (n < 0) { if (errno==EINTR) continue; close(d); return -1; }
        char *p = buf; ssize_t m = n;
        while (m > 0) {
            ssize_t w = write(d, p, m);
            if (w < 0) { if (errno==EINTR) continue; close(d); return -1; }
            p += w; m -= w;
        }
    }
    fsync(d);
    (void)fchmod(d, 0400); //10-27
    close(d);
    return 0;
}

/* 在 /var/log/audit 里按 inode 找已轮转文件名 */
static int find_rotated_name_by_inode(char out_path[PATH_MAX], dev_t dev, ino_t ino)
{
    DIR *d = opendir("/var/log/audit");
    if (!d) return -1;
    struct dirent *de;
    struct stat st;
    while ((de = readdir(d))) {
        if (strncmp(de->d_name, "audit.log", 9) != 0) continue;
        char cand[PATH_MAX];
        snprintf(cand, sizeof(cand), "/var/log/audit/%s", de->d_name);
        if (stat(cand, &st) != 0) continue;
        if (st.st_dev == dev && st.st_ino == ino) {
            strncpy(out_path, cand, PATH_MAX-1);
            out_path[PATH_MAX-1] = 0;
            closedir(d);
            return 0;
        }
    }
    closedir(d);
    return -1;
}


/* 把 src_idx 的前 keep_i 条搬到 dst_idx，但严格按“快照文件实际存在的行数”裁剪；
 * 不再为超出快照行数的条目生成合成 off。最终写入条数 = min(keep_i(或全部), 快照实际行数, 源 idx 条数) */
/* 把 src_idx 的前 keep_i 条搬到 dst_idx：
 * - lhash/chash：严格沿用源 idx（代表“旧期望”）；
 * - off：对快照里真实存在的前 k 行写真实偏移；k 之后用严格递增的合成 offset。
 * 最终写入条数 = min(keep_i(或全部), 源 idx 条数) —— 注意，不截断到快照实际行数。
 */
static int build_idx_for_snapshot(const char *src_idx,
                                  const char *snapshot_log_path,
                                  const char *dst_idx,
                                  unsigned long long keep_i)
{
    IdxArray arr = {0};
    if (load_idx_entries(src_idx, &arr) != 0) {
        /* 源 idx 暂不可读/不存在 —— 按 0 条处理（仍生成一个合法的空 idx） */
        memset(&arr, 0, sizeof(arr));
    }

    unsigned long long max_from_src = (unsigned long long)arr.count;
    unsigned long long want = (keep_i == ULLONG_MAX) ? max_from_src : keep_i;
    if (want > max_from_src) want = max_from_src;

    /* 打开快照 log，尽量给前 want 行提供真实 off；want==0 也要生成空 idx 文件 */
    FILE *lf = fopen(snapshot_log_path, "rb");
    if (!lf) { free_idx_array(&arr); return -1; }

    /* 至少分配 1 个元素，便于统一 free */
    unsigned long long *offs = (unsigned long long *)calloc((size_t)(want ? want : 1), sizeof(*offs));
    if (!offs) { fclose(lf); free_idx_array(&arr); return -1; }

    char *line = NULL; size_t cap = 0; ssize_t m;
    unsigned long long off = 0;
    size_t k = 0;
    while (k < want && (m = getline(&line, &cap, lf)) != -1) {
        offs[k] = off;
        off += (unsigned long long)m;
        k++;
    }
    free(line);
    fclose(lf);

    FILE *out = fopen(dst_idx, "w");
    if (!out) { free(offs); free_idx_array(&arr); return -1; }
    chmod(dst_idx, 0600);

    /* 对于 “快照不足 want 行”的尾部，用严格递增的合成 off（与旧实现一致）。*/
    unsigned long long synth_off = (k > 0) ? (offs[k - 1] + 1) : 0;

    for (size_t i = 0; i < (size_t)want; i++) {
        unsigned long long o = (i < k) ? offs[i] : synth_off++;
        if (fprintf(out,
                    "i=%llu off=%llu lhash=%s chash=%s\n",
                    (unsigned long long)(i + 1),
                    o,
                    arr.entries[i].lhash,
                    arr.entries[i].chash) < 0) {
            fclose(out);
            unlink(dst_idx);
            free(offs);
            free_idx_array(&arr);
            return -1;
        }
    }

    fflush(out); fsync(fileno(out)); fclose(out);
    chmod(dst_idx, 0400);

    free(offs);
    free_idx_array(&arr);
    return 0;
}




/* 写 seal（定制：records 和 chain 由调用者指定） */
static int gmseal_write_seal_custom(const char *log_path_for_canon,
                                    const char *idx_path_for_hash,
                                    const char *seal_out_path,
                                    unsigned long long records_i,
                                    const char *chain_hex_in)
{
    // if (g_degraded_mode) {
    //     log_msg("In degraded mode: skip writing seal_custom");
    //     return 0;
    // }

    int rc = -1;
    EVP_PKEY *pkey = NULL;
    X509 *cert = NULL;
    char *pass_in = NULL;

    /* --- 读取 PASS_PATH --- */
    {
        FILE *pf = fopen(PASS_PATH,"r");
        if (!pf) { log_msg("open PASS_PATH failed"); return -1; }
        size_t cap = 0; ssize_t n = getline(&pass_in, &cap, pf);
        fclose(pf);
        if (n <= 0) { free(pass_in); log_msg("read PASS_PATH empty"); return -1; }
        while (n && (pass_in[n-1]=='\n'||pass_in[n-1]=='\r')) pass_in[--n]=0;
    }

    /* --- 读取 salt --- */
    unsigned char salt[SALT_BYTES];
    {
        FILE *sf = fopen(SALT_PATH,"r");
        if (!sf) { log_msg("open SALT_PATH failed"); free(pass_in); return -1; }
        char saltbuf[256];
        if (!fgets(saltbuf,sizeof(saltbuf),sf)) { fclose(sf); free(pass_in); return -1; }
        fclose(sf);
        size_t slen = strlen(saltbuf);
        while (slen && (saltbuf[slen-1]=='\n'||saltbuf[slen-1]=='\r')) saltbuf[--slen]=0;
        if (slen != (size_t)SALT_BYTES*2) { log_msg("salt length error"); free(pass_in); return -1; }
        for (size_t i=0;i<SALT_BYTES;i++){
            unsigned int hi,lo;
            if (sscanf(&saltbuf[i*2], "%1x%1x", &hi,&lo) != 2) { free(pass_in); return -1; }
            salt[i] = (hi<<4)|lo;
        }
    }

    int is_hex64 = (strlen(pass_in)==64);
    if (is_hex64) {
        for (int i=0;i<64;i++) {
            char c = pass_in[i];
            if (!((c>='0'&&c<='9')||(c>='a'&&c<='f'))) { is_hex64 = 0; break; }
        }
    }

    char passhex[DERIVE_BYTES*2+1] = {0};
    int derived_from_raw = 0;

    if (is_hex64) {
        strncpy(passhex, pass_in, sizeof(passhex)-1);
    } else {
        const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
        if (!sm3) { log_msg("no sm3"); OPENSSL_cleanse(pass_in, strlen(pass_in)); free(pass_in); return -1; }
        unsigned char derived[DERIVE_BYTES];
        if (!PKCS5_PBKDF2_HMAC(pass_in, strlen(pass_in), salt, SALT_BYTES,
                               DERIVE_ITERATIONS, sm3, DERIVE_BYTES, derived)) {
            log_msg("PBKDF2-SM3 failed");
            OPENSSL_cleanse(pass_in, strlen(pass_in)); free(pass_in);
            return -1;
        }
        hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
        derived_from_raw = 1;
    }

    {
        FILE *pf2 = fopen(P12_PATH,"rb");
        if (!pf2) { log_msg("open P12_PATH failed"); OPENSSL_cleanse(pass_in, strlen(pass_in)); free(pass_in); return -1; }
        PKCS12 *p12 = d2i_PKCS12_fp(pf2, NULL); fclose(pf2);
        if (!p12) { log_msg("d2i_PKCS12_fp failed"); OPENSSL_cleanse(pass_in, strlen(pass_in)); free(pass_in); return -1; }

        if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL)) {
            if (!is_hex64 && derived_from_raw) {
                log_msg("PKCS12_parse failed with derived hex, retry with PASS_PATH raw");
                if (!PKCS12_parse(p12, pass_in, &pkey, &cert, NULL)) {
                    log_msg("PKCS12_parse failed with PASS_PATH raw as well");
                    PKCS12_free(p12);
                    OPENSSL_cleanse(pass_in, strlen(pass_in)); free(pass_in);
                    return -1;
                }
            } else {
                log_msg("PKCS12_parse failed");
                PKCS12_free(p12);
                OPENSSL_cleanse(pass_in, strlen(pass_in)); free(pass_in);
                return -1;
            }
        }
        PKCS12_free(p12);
    }

    OPENSSL_cleanse(pass_in, strlen(pass_in));
    free(pass_in);

    /* --- 证书指纹 --- */
    unsigned char *der=NULL; int derlen = i2d_X509(cert, &der);
    if (derlen <= 0) { EVP_PKEY_free(pkey); X509_free(cert); return -1; }
    EVP_MD_CTX *ctx = EVP_MD_CTX_new(); unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
    if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
        EVP_DigestUpdate(ctx, der, derlen) != 1 || EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1) {
        if (ctx) EVP_MD_CTX_free(ctx); OPENSSL_free(der); EVP_PKEY_free(pkey); X509_free(cert); return -1;
    }
    EVP_MD_CTX_free(ctx); OPENSSL_free(der);
    char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));

    /* --- index_sm3 --- */
    char idx_sm3[65] = {0};
    if (sm3_file_hex(idx_path_for_hash, idx_sm3) != 0) {
        EVP_PKEY_free(pkey); X509_free(cert);
        log_msg("sm3 idx failed for %s", idx_path_for_hash);
        return -1;
    }

    const char *chain_hex_use =
        (chain_hex_in && chain_hex_in[0]) ? chain_hex_in :
        "0000000000000000000000000000000000000000000000000000000000000000";

    char *canon = NULL;
    if (asprintf(&canon,
        "version=1\nhash=sm3\nsign=sm2\nsm2_id=%s\npath=%s\nrecords=%llu\nchain=%s\nkey_fpr=%s\nindex_sm3=%s\n",
        SM2_ID, log_path_for_canon, (unsigned long long)records_i, chain_hex_use, fpr_hex, idx_sm3) < 0) {
        EVP_PKEY_free(pkey); X509_free(cert); return -1;
    }

    unsigned char *sig=NULL; size_t siglen = 0;
    if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon), SM2_ID, &sig, &siglen) != 0) {
        log_msg("gm_sm2_sign failed"); free(canon); EVP_PKEY_free(pkey); X509_free(cert); return -1;
    }
    char *sigb64 = b64_encode(sig, siglen);
    OPENSSL_free(sig);

    unsigned char *der2=NULL; int der2len = i2d_X509(cert, &der2);
    char *certb64 = NULL;
    if (der2len > 0) { certb64 = b64_encode(der2, der2len); OPENSSL_free(der2); }
    else { log_msg("i2d_X509 failed"); free(canon); free(sigb64); EVP_PKEY_free(pkey); X509_free(cert); return -1; }

    EVP_PKEY_free(pkey); X509_free(cert);

    char tmp[PATH_MAX]; snprintf(tmp,sizeof(tmp), "%s.tmp", seal_out_path);
    FILE *sfw = fopen(tmp, "w");
    if (!sfw) { log_msg("open seal tmp failed: %s", strerror(errno)); free(canon); free(sigb64); free(certb64); return -1; }
    fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n%s", canon);
    fprintf(sfw, "cert_der_base64=%s\n", certb64);
    fprintf(sfw, "sig_base64=%s\n", sigb64);
    fclose(sfw);
    //chmod(tmp, 0600);  //10-27
    chmod(tmp, 0400);
    if (rename(tmp, seal_out_path) != 0) {
        log_msg("rename seal tmp -> %s failed: %s", seal_out_path, strerror(errno));
        unlink(tmp); free(canon); free(sigb64); free(certb64); return -1;
    }
    free(canon); free(sigb64); free(certb64);

    log_msg("seal_custom written: %s (records=%llu path=%s)", seal_out_path, (unsigned long long)records_i, log_path_for_canon);
    rc = 0;
    return rc;
}

/* ========== 去抖/稳定等待（更保守）：连续 probes 次满足 size/mtime 不变才算稳定 ========== */
/* ========== 更稳的“稳定等待”：同时观测 LOG 和 IDX，达标后二次复核 ========== */
static int wait_file_stable(const char *path, int probes, int gap_ms)
{
    struct stat prev_log, cur_log;
    struct stat prev_idx, cur_idx;
    int have_idx = 0;

    if (stat(path, &prev_log) != 0) return -1;

    /* 当观察 audit.log 时，顺带观察 idx 是否也稳定 */
    if (strcmp(path, LOG_PATH) == 0) {
        if (stat(IDX_PATH, &prev_idx) == 0) have_idx = 1;
    }

    int streak = 0;
    int waited_ms = 0;
    const int soft_timeout_ms = 15000; /* 最多等 15s，防止极端场景卡死 */

    for (;;) {
        usleep(gap_ms * 1000);
        waited_ms += gap_ms;

        if (stat(path, &cur_log) != 0) return -1;

        /* 动态纳入/剔除 idx 的观测 */
        if (!have_idx && strcmp(path, LOG_PATH) == 0) {
            if (stat(IDX_PATH, &cur_idx) == 0) {
                prev_idx = cur_idx;
                have_idx = 1;
                streak = 0;
            }
        } else if (have_idx) {
            if (stat(IDX_PATH, &cur_idx) != 0) {
                have_idx = 0;
                streak = 0;
            }
        }

        int log_same = (cur_log.st_mtime == prev_log.st_mtime &&
                        cur_log.st_size  == prev_log.st_size);
        int idx_same = 1; /* 没有 idx 也视为稳定 */
        if (have_idx) {
            idx_same = (cur_idx.st_mtime == prev_idx.st_mtime &&
                        cur_idx.st_size  == prev_idx.st_size);
        }

        if (log_same && idx_same) {
            if (++streak >= probes) {
                /* 二次复核 */
                usleep(gap_ms * 1000);

                struct stat chk_log, chk_idx;
                if (stat(path, &chk_log) != 0) return -1;

                int ok_log = (chk_log.st_mtime == cur_log.st_mtime &&
                              chk_log.st_size  == cur_log.st_size);
                int ok_idx = 1;
                if (have_idx) {
                    if (stat(IDX_PATH, &chk_idx) != 0) {
                        ok_idx = 0;
                    } else {
                        ok_idx = (chk_idx.st_mtime == cur_idx.st_mtime &&
                                  chk_idx.st_size  == cur_idx.st_size);
                    }
                }

                if (ok_log && ok_idx) return 0;
                prev_log = chk_log;
                if (have_idx) prev_idx = chk_idx;
                streak = 0;
            }
        } else {
            prev_log = cur_log;
            if (have_idx) prev_idx = cur_idx;
            streak = 0;
        }

        if (waited_ms >= soft_timeout_ms) return 0;
    }
}


/* 只复制 src 的前 nlines 行到 dst；nlines==0 表示复制全部 */
static int copy_first_n_lines(const char *src, const char *dst, unsigned long long nlines,
                              unsigned long long *out_written_lines)
{
    int s = open(src, O_RDONLY);
    if (s < 0) return -1;
    int d = open(dst, O_WRONLY|O_CREAT|O_TRUNC, 0600);
    if (d < 0) { close(s); return -1; }

    char buf[1<<16];
    unsigned long long lines = 0;
    ssize_t nr;
    int done = 0;

    while (!done && (nr = read(s, buf, sizeof(buf))) > 0) {
        char *p = buf, *q, *seg = buf;
        ssize_t left = nr;

        while (!done && left > 0) {
            q = memchr(p, '\n', left);
            if (!q) break;

            lines++;
            p = q + 1;
            left = (ssize_t)((buf + nr) - p);

            if (nlines && lines == nlines) {
                /* 把 [seg .. p) 写出去后就结束 */
                ssize_t to_write = (ssize_t)(p - seg);
                char *w = seg;
                while (to_write > 0) {
                    ssize_t wv = write(d, w, to_write);
                    if (wv < 0) { if (errno==EINTR) continue; close(s); close(d); return -1; }
                    w += wv; to_write -= wv;
                }
                done = 1;
                break;
            }
        }

        if (!done) {
            /* 这一块没有达到 nlines 上限，整块写出 */
            ssize_t to_write = nr;
            char *w = buf;
            while (to_write > 0) {
                ssize_t wv = write(d, w, to_write);
                if (wv < 0) { if (errno==EINTR) continue; close(s); close(d); return -1; }
                w += wv; to_write -= wv;
            }
        }
    }
    if (nr < 0) { close(s); close(d); return -1; }

    fsync(d);
    close(s);
    close(d);
    if (out_written_lines) *out_written_lines = lines;
    return 0;
}

/* ===== 新增：严格校验 idx 与 audit.log 前缀一致性 =====
 * 规则：
 *   1) 若当前存在 SEAL，则仅校验前 seal.records 条（已被 seal 保护的前缀）
 *   2) 若不存在 SEAL，则校验整个 idx（首次运行场景）
 * 返回：
 *   0  = 前缀一致，允许继续写 seal
 *  -1  = 发生错误（IO/解析/计算失败）
 *  -2  = 校验失败（idx 被中途改写/与 log 不符），应进入 degraded，禁止写 seal
 */
//-------------------------------------2025---------------------------------
static int verify_idx_against_log_prefix(void)
{
    /* 读现有 seal，拿到被保护的前缀条数 */
    unsigned long long seal_rec = 0ULL;
    struct seal S = {0};
    if (parse_seal_file(SEAL_PATH, &S) == 0) {
        if (S.records) seal_rec = strtoull(S.records, NULL, 10);
        free_seal(&S);
    }

    /* 载入 idx */
    IdxArray arr = {0};
    if (load_idx_entries(IDX_PATH, &arr) != 0) {
        free_idx_array(&arr);
        return -1;
    }
    unsigned long long to_check = (seal_rec > 0) ? seal_rec : (unsigned long long)arr.count;

    // PATCH: 避免极端比赛条件（idx 已重建但 log 又在涨）//11-6
    unsigned long long log_lines_now = count_file_lines_simple(LOG_PATH);
    if (to_check > log_lines_now) to_check = log_lines_now;

    if (to_check == 0) { /* 空前缀，直接通过 */
        free_idx_array(&arr);
        return 0;
    }
    if (to_check > (unsigned long long)arr.count) {
        /* seal.records 不应大于当前 idx 条数；这说明 idx 被截断/损坏 */
        free_idx_array(&arr);
        return -2;
    }

    /* 打开 audit.log，按 idx.off 精准读取行 */
    FILE *lf = fopen(LOG_PATH, "rb");
    if (!lf) { free_idx_array(&arr); return -1; }

    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3) { fclose(lf); free_idx_array(&arr); return -1; }

    /* 滚动链初始为 32 字节 0（与你现有链定义一致） */
    unsigned char H[32]; memset(H, 0, sizeof(H));

    char *line = NULL; size_t cap = 0;
    for (unsigned long long k = 0; k < to_check; k++) {
        const IdxEntry *e = &arr.entries[k];

        /* off 必须单调非减（常见是严格递增；这里允许等于，容错极小概率换行差异） */
        if (k > 0 && e->off < arr.entries[k-1].off) {
            fclose(lf); free(line); free_idx_array(&arr);
            return -2;
        }

        /* 定位并读一行 */
        if (fseeko(lf, (off_t)e->off, SEEK_SET) != 0) {
            fclose(lf); free(line); free_idx_array(&arr);
            return -1;
        }
        ssize_t n = getline(&line, &cap, lf);
        if (n <= 0) {
            fclose(lf); free(line); free_idx_array(&arr);
            return -2; /* off 指向失效或越界 */
        }
        size_t norm = normalize_line(line, (size_t)n);

        /* 计算 lhash（行哈希）并与 idx 比对 */
        unsigned char L[32]; unsigned int lm = 0;
        EVP_MD_CTX *c1 = EVP_MD_CTX_new();
        if (!c1 ||
            EVP_DigestInit_ex(c1, sm3, NULL) != 1 ||
            EVP_DigestUpdate(c1, line, norm) != 1 ||
            EVP_DigestFinal_ex(c1, L, &lm) != 1 || lm != 32) {
            if (c1) EVP_MD_CTX_free(c1);
            fclose(lf); free(line); free_idx_array(&arr);
            return -1;
        }
        EVP_MD_CTX_free(c1);

        char lhex[65]; hexdump(L, 32, lhex, sizeof(lhex));
        if (strncmp(lhex, e->lhash, 64) != 0) {
            fclose(lf); free(line); free_idx_array(&arr);
            return -2; /* 中段被改写：lhash 不一致 */
        }

        /* 滚动链：H = SM3(H || line)，并与 idx.chash 比对 */
        unsigned char newH[32]; unsigned int hm = 0;
        EVP_MD_CTX *c2 = EVP_MD_CTX_new();
        if (!c2 ||
            EVP_DigestInit_ex(c2, sm3, NULL) != 1 ||
            EVP_DigestUpdate(c2, H, sizeof(H)) != 1 ||
            EVP_DigestUpdate(c2, line, norm) != 1 ||
            EVP_DigestFinal_ex(c2, newH, &hm) != 1 || hm != 32) {
            if (c2) EVP_MD_CTX_free(c2);
            fclose(lf); free(line); free_idx_array(&arr);
            return -1;
        }
        EVP_MD_CTX_free(c2);

        char chex[65]; hexdump(newH, 32, chex, sizeof(chex));
        if (strncmp(chex, e->chash, 64) != 0) {
            fclose(lf); free(line); free_idx_array(&arr);
            return -2; /* 中段被改写：chash 不一致 */
        }

        memcpy(H, newH, 32);
    }

    fclose(lf);
    free(line);
    free_idx_array(&arr);
    return 0; /* 前缀一致，可写 seal */
}


/* ---- 写 seal 前只读预检（增强版） ----
 * 1) 先对“已被 seal 保护的前缀（或无 seal 时的整个 idx）”做一致性校验
 * 2) 再做原有的 tail 采样校验
 * 任一失败 -> 返回 -1，调用方进入 degraded 并拒绝写 seal
 */
//-------------------------------------2025---------------------------------
static int safe_pre_seal_check(void)
{
    if (g_degraded_mode) return -1;

    /* A. 先严审 idx 与 log 的前缀（阻止“中段被改写后刷新 seal 洗白”） */
    int vc = verify_idx_against_log_prefix();
    if (vc != 0) {
        /* -2：内容不一致；-1：异常。两者都视为不安全 */
        return -1;
    }

    /* B. 保留你的尾项采样校验（轻量、兜底） */
    if (g_lines == 0) return 0;
    if (!g_log_fp && open_log_handle() != 0) return -1;

    IdxEntry tail = {0};
    if (read_last_idx_entry(&tail) != 0) return -1;

    if (fseeko(g_log_fp, (off_t)tail.off, SEEK_SET) != 0) return -1;

    char *line = NULL; size_t cap = 0; ssize_t n = getline(&line, &cap, g_log_fp);
    if (n <= 0) { free(line); return -1; }
    size_t norm = normalize_line(line,(size_t)n);
    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    unsigned char md[32]; unsigned int mdlen=0;
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    int ok = ctx && sm3 &&
             EVP_DigestInit_ex(ctx, sm3, NULL)==1 &&
             EVP_DigestUpdate(ctx, line, norm)==1 &&
             EVP_DigestFinal_ex(ctx, md, &mdlen)==1 && mdlen==32;
    if (ctx) EVP_MD_CTX_free(ctx);
    char lhex_now[65]={0};
    if (ok) hexdump(md,32,lhex_now,sizeof(lhex_now));
    free(line);
    if (!ok || strcmp(lhex_now, tail.lhash) != 0) return -1;

    return 0;
}

/* ---- 写主 seal（records/chain 以 idx 尾项为准；仅在前缀一致时写） ---- */
static int gmseal_write_seal(void)
{
    // if (g_degraded_mode) {
    //     log_msg("In degraded mode: skip writing seal");
    //     return 0;
    // } 
    //11-6
    if (g_degraded_mode) {
        if (verify_idx_against_log_prefix() == 0) {
            g_degraded_mode = 0;
            log_msg("degraded -> ok (prefix verified); proceed to write seal");
        } else {
            log_msg("In degraded mode and prefix not safe; skip writing seal");
            return 0;
        }
    }


    /* 先做增强型预检（含前缀一致性检查） */
    if (safe_pre_seal_check() != 0) {
        log_msg("safe_pre_seal_check failed; entering degraded mode");
        enter_degraded_mode("pre-seal check failed");
        return -1;
    }

    /* 以 idx 尾项为准，生成 canon 中的 records/chain */
    IdxEntry tail = {0};
    if (read_last_idx_entry(&tail) != 0) {
        /* idx 为空的场景：records=0, chain=全0 */
        memset(&tail, 0, sizeof(tail));
    }

    /* 暂存当前 g_lines/g_chain，避免误导；真正入 seal 的值完全来自 idx */
    unsigned long long save_lines = g_lines;
    unsigned char save_chain[32]; memcpy(save_chain, g_chain, 32);

    /* 把 idx 尾项的 i/chash 投影进全局，只用于写 canon（不影响其余流程） */
    g_lines = tail.i; /* 为空时是 0 */
    if (tail.chash[0]) {
        /* 把 hex chash -> bytes 放到 g_chain，便于后续 gmseal_write_seal_for 使用 */
        for (int i = 0; i < 32; i++) {
            unsigned int hi, lo;
            if (sscanf(&tail.chash[i*2], "%1x%1x", &hi, &lo) != 2) { hi = lo = 0; }
            g_chain[i] = (unsigned char)((hi << 4) | lo);
        }
    } else {
        memset(g_chain, 0, 32);
    }

    int rc = gmseal_write_seal_for(LOG_PATH, IDX_PATH, SEAL_PATH);

    /* 恢复现场，避免影响其它路径使用 g_lines/g_chain 的逻辑 */
    g_lines = save_lines;
    memcpy(g_chain, save_chain, 32);

    return rc;
}

//-------------------------------------2025---------------------------------


/* 把“inode 不变时的可疑追加行”记录到一个独立文件：
 *   /var/log/audit/audit.tamperlines
 * 记录格式（单行）：
 *   ts=YYYY-mm-ddTHH:MM:SS i=<下一条行号> off=<偏移> ino=<inode>
 *   len=<normlen> lhash=<SM3(line)>
 *   text="<可读预览，非打印符号以\\xHH转义，最多256字节>"
 *
 * 注意：
 * - 只在“本代已出现过至少一条正常审计行”后才落盘（g_lines>0），
 *   避免把 inode 变化后的首批行写进去。
 * - inode 改变的场景仍由 check_rotation_and_rollover() 负责，
 *   本函数不额外处理。
 */
/* 处理一行（带“基线偏移”抑制机制） */
static int gmseal_on_line_from_logbuf(const char *buf, size_t raw_n)
{
    if (g_degraded_mode) return 0;

    /* ==== 基线抑制：识别 inode 变化或就地截断，计算 ignore_before_off ==== */
    static dev_t s_seen_dev = 0;
    static ino_t s_seen_ino = 0;
    static unsigned long long s_ignore_before_off = 0; /* 本代开始时的文件大小（字节） */
    static unsigned long long s_last_offset = 0;       /* 上次调用结束后的 g_offset_raw */

    struct stat st_now;
    if (stat(LOG_PATH, &st_now) == 0) {
        int ino_changed = (s_seen_dev != st_now.st_dev) || (s_seen_ino != st_now.st_ino);
        int rewinded     = (!ino_changed && s_last_offset > 0 && g_offset == 0);
        /* 1) inode 变了：新一代 -> 把“当下已存在”的所有字节视为基线，后续才算追加 */
        if (ino_changed) {
            s_seen_dev = st_now.st_dev;
            s_seen_ino = st_now.st_ino;
            s_ignore_before_off = (unsigned long long)st_now.st_size;
            s_last_offset = 0;
            log_msg("tamperlines: new inode detected (ino=%ju), baseline=%llu bytes",
                    (uintmax_t)s_seen_ino, (unsigned long long)s_ignore_before_off);
        }
        /* 2) inode 未变但 offset 被重置（就地截断触发了新一代）：同样重置基线 */
        else if (rewinded) {
            s_ignore_before_off = (unsigned long long)st_now.st_size;
            s_last_offset = 0;
            log_msg("tamperlines: same inode but offset rewind; baseline=%llu bytes",
                    (unsigned long long)s_ignore_before_off);
        }
    }

    /* ==== 规范化长度（去 \n/\r）==== */
    size_t normlen = raw_n;
    while (normlen > 0 && (buf[normlen-1]=='\n' || buf[normlen-1]=='\r')) normlen--;

    /* ==== 轻量启发式：不像审计行就判定为“可疑追加”候选 ==== */
    int suspicious_line = 0;
    if (normlen == 0) {
        suspicious_line = 1;
    } else {
        void *p1 = memmem(buf, normlen, "type=", 5);
        void *p2 = memmem(buf, normlen, "msg=", 4);
        if (!p1 || !p2) suspicious_line = 1;
    }

    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3) return -1;

    /* ==== 计算本行 lhash ==== */
    unsigned char lhash[32]; unsigned int lm=0;
    EVP_MD_CTX *c1 = EVP_MD_CTX_new();
    if (!c1) return -1;
    if (EVP_DigestInit_ex(c1, sm3, NULL) != 1 ||
        EVP_DigestUpdate(c1, buf, normlen) != 1 ||
        EVP_DigestFinal_ex(c1, lhash, &lm) != 1 || lm != 32) {
        EVP_MD_CTX_free(c1); return -1;
    }
    EVP_MD_CTX_free(c1);
    char lhex[65]; hexdump(lhash, 32, lhex, sizeof(lhex));

    /* ==== 仅当：可疑行 && 本行“起始偏移” >= 基线偏移，才记录到 audit.tamperlines ==== */
    if (suspicious_line && (g_offset >= s_ignore_before_off)) {
        /* 生成可读预览（<=256 字节，非打印转义） */
        size_t sample = normlen > 256 ? 256 : normlen;
        char preview[256 * 4 + 1];
        size_t pj = 0;
        static const char hx[] = "0123456789abcdef";
        for (size_t i = 0; i < sample && pj + 4 < sizeof(preview); i++) {
            unsigned char c = (unsigned char)buf[i];
            if (c == '\\' || c == '\"') {
                if (pj + 2 >= sizeof(preview)) break;
                preview[pj++] = '\\';
                preview[pj++] = (char)c;
            } else if (c >= 0x20 && c <= 0x7e) {
                preview[pj++] = (char)c;
            } else {
                if (pj + 4 >= sizeof(preview)) break;
                preview[pj++] = '\\';
                preview[pj++] = 'x';
                preview[pj++] = hx[(c>>4)&0xF];
                preview[pj++] = hx[c&0xF];
            }
        }
        preview[pj] = 0;

        const char *TLP = "/var/log/audit/suspicious-lines";
        int tfd = open(TLP, O_WRONLY | O_CREAT | O_APPEND, 0600);
        if (tfd >= 0) {
            time_t now = time(NULL);
            struct tm tmv; localtime_r(&now, &tmv);
            char iso[32]; strftime(iso, sizeof(iso), "%Y-%m-%dT%H:%M:%S", &tmv);

            if (flock(tfd, LOCK_EX) == 0) {
                dprintf(tfd,
                        "ts=%s i=%llu off=%llu ino=%ju len=%zu lhash=%s text=\"%s\"\n",
                        iso,
                        (unsigned long long)(g_lines + 1),
                        (unsigned long long)g_offset,
                        (uintmax_t)st_now.st_ino,
                        (size_t)normlen,
                        lhex,
                        preview);
                fsync(tfd);
                flock(tfd, LOCK_UN);
            }
            close(tfd);
        } else {
            log_msg("open tamperlines failed: %s", strerror(errno));
        }
    }

    /* ==== 以下保持你原本“入链 + 写 idx”的流程（不动）==== */
    unsigned char cand_chain[32]; unsigned int hm=0;
    EVP_MD_CTX *c2 = EVP_MD_CTX_new();
    if (!c2) return -1;
    if (EVP_DigestInit_ex(c2, sm3, NULL) != 1 ||
        EVP_DigestUpdate(c2, g_chain, sizeof(g_chain)) != 1 ||
        EVP_DigestUpdate(c2, buf, normlen) != 1 ||
        EVP_DigestFinal_ex(c2, cand_chain, &hm) != 1 || hm != 32) {
        EVP_MD_CTX_free(c2); return -1;
    }
    EVP_MD_CTX_free(c2);

    char chex[65]; hexdump(cand_chain, 32, chex, sizeof(chex));
    if (g_idx_fd < 0 && open_idx_handle() != 0) {
        log_msg("gmseal_on_line: open_idx_handle failed");
        return -1;
    }

    IdxEntry tail = {0};
    if (read_last_idx_entry(&tail) == 0) {
        // if (g_lines == tail.i && g_offset <= tail.off) {
        //     log_msg("gmseal_on_line: non-monotonic offset (g_offset=%llu <= tail.off=%llu), skip duplicate",
        //             (unsigned long long)g_offset, (unsigned long long)tail.off);
        //     s_last_offset = g_offset + (unsigned long long)raw_n; /* 维护内部状态 */
        //     return 0;
        // }
        // if (tail.i == g_lines && strncmp(tail.chash, chex, sizeof(tail.chash)) == 0) {
        //     s_last_offset = g_offset + (unsigned long long)raw_n;
        //     return 0;
        // }

        //11-6
        // 仅当“下一条行号相等且 offset 也正好相等”才认为是同一行的重复回放
        if (tail.i == g_lines && tail.off == g_offset &&
            strncmp(tail.chash, chex, 64) == 0) {
            // 真正的重复，跳过
            return 0;
        }
    }

    if (append_idx_line_atomic(g_lines + 1, g_offset, lhex, chex) != 0) {
        log_msg("gmseal_on_line: append_idx_line_atomic failed for next i=%llu",
                (unsigned long long)(g_lines + 1));
        return -1;
    }

    memcpy(g_chain, cand_chain, 32);
    g_lines++;
    g_offset += (unsigned long long)raw_n;

    /* 更新内部“上次偏移” */
    s_last_offset = g_offset;

    return 0;
}



//-------------------------------------2025---------------------------------

/* 把可读尾部吃完（带运行期前置校验） */
static int process_all_available_lines(void)
{
    if (g_degraded_mode) return 0;
    if (!g_log_fp && open_log_handle() != 0) return -1;

    if (g_lines > 0) {
        IdxEntry tail = {0};
        if (read_last_idx_entry(&tail) != 0) {
            log_msg("preflight: cannot read last idx entry");
            enter_degraded_mode("Runtime preflight failed: cannot read last idx entry");
            return -1;
        }
        if (fseeko(g_log_fp, (off_t)tail.off, SEEK_SET) != 0) {
            log_msg("preflight: fseeko to tail.off=%llu failed: %s",
                    (unsigned long long)tail.off, strerror(errno));
            enter_degraded_mode("preflight fseeko failed");
            return 0;
        }
        char *line = NULL; size_t cap = 0; ssize_t n = getline(&line, &cap, g_log_fp);
        if (n <= 0) {
            free(line);
            log_msg("preflight: cannot read line at off=%llu", (unsigned long long)tail.off);
            enter_degraded_mode("preflight cannot read last line");
            return 0;
        }
        size_t norm = normalize_line(line, (size_t)n);
        const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
        unsigned char md[32]; unsigned int mdlen = 0;
        EVP_MD_CTX *ctx = EVP_MD_CTX_new();
        int ok = ctx && sm3 &&
                 EVP_DigestInit_ex(ctx, sm3, NULL)==1 &&
                 EVP_DigestUpdate(ctx, line, norm)==1 &&
                 EVP_DigestFinal_ex(ctx, md, &mdlen)==1 && mdlen==32;
        if (ctx) EVP_MD_CTX_free(ctx);
        char lhex_now[65]={0};
        if (ok) hexdump(md,32,lhex_now,sizeof(lhex_now));

        if (!ok || strcmp(lhex_now, tail.lhash) != 0) {
            log_msg("preflight: last processed line content mismatch (idx lhash=%s, now=%s)",
                    tail.lhash, lhex_now[0]?lhex_now:"<err>");
            free(line);
            enter_degraded_mode("preflight mismatch at runtime");
            return 0;
        }

        g_offset = tail.off + (unsigned long long)n;
        free(line);
    }

    if (fseeko(g_log_fp, (off_t)g_offset, SEEK_SET) != 0) {
        log_msg("process_all_available_lines: fseeko failed: %s", strerror(errno));
        return -1;
    }
    int consumed = 0;
    char *line = NULL; size_t cap = 0; ssize_t n;
    while ((n = getline(&line, &cap, g_log_fp)) > 0) {
        if (gmseal_on_line_from_logbuf(line, (size_t)n) != 0) {
            log_msg("process_all_available_lines: on_line failed");
            free(line); return -1;
        }
        consumed++;
    }
    free(line);
    /* 新增：如果本轮读到新行（包括正常审计追加），且 suspicious-lines 非空，则做一次重定位 */
    if (consumed > 0) {
        struct stat st_sl;
        if (stat("/var/log/audit/suspicious-lines", &st_sl) == 0 && st_sl.st_size > 0) {
            (void)rebase_tamperlines_to_current_log();
        }
    }
    return consumed;
}

//-------------------------------------2025---------------------------------


//11-6降级恢复

// NEW: 总是从当前磁盘状态重开句柄并同步到 idx 尾部
static int resync_from_disk_tail(int force_reopen)
{
    if (force_reopen) {
        close_log_handle();
        close_idx_handle();
    }
    if (open_log_handle() != 0)  return -1;
    if (open_idx_handle() != 0)  return -1;

    unsigned long long last_i = 0, last_off = 0;
    unsigned char chain[32] = {0};
    if (recover_tail_from_idx(&last_i, &last_off, chain) != 0) {
        // idx 可能为空（首跑），允许回到零基线
        memset(chain, 0, sizeof(chain));
        last_i = 0;
        last_off = 0;
    }

    g_lines  = last_i;
    g_offset = last_off;
    memcpy(g_chain, chain, sizeof(g_chain));

    // 把 g_log_fp 定位到新 offset，防止下一轮 getline 从错误位置开始
    if (g_log_fp && fseeko(g_log_fp, (off_t)g_offset, SEEK_SET) != 0) {
        return -1;
    }

    log_msg("resync_from_disk_tail: lines=%llu off=%llu",
            (unsigned long long)g_lines, (unsigned long long)g_offset);
    return 0;
}

/* === NEW: 尝试退出降级（仅在磁盘状态已被外部修复时） === */
static int try_exit_degraded_if_repaired(void)
{
    if (!g_degraded_mode) return 0;

    (void)resync_from_disk_tail(/*force_reopen=*/1);

    /* 先做严审：idx 与 log 的“受保护前缀”一致（你已有的实现） */
    if (verify_idx_against_log_prefix() != 0) {
        return -1;  /* 仍不安全，保持降级 */
    }

    /* 再做尾项只读复核：idx 尾项的那一行在 log 里内容未变 */
    IdxEntry tail = {0};
    if (read_last_idx_entry(&tail) != 0) return -1;
    if (!g_log_fp && open_log_handle() != 0) return -1;
    if (fseeko(g_log_fp, (off_t)tail.off, SEEK_SET) != 0) return -1;

    char *line = NULL; size_t cap = 0; ssize_t n = getline(&line, &cap, g_log_fp);
    if (n <= 0) { free(line); return -1; }
    size_t norm = normalize_line(line, (size_t)n);

    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    unsigned char md[32]; unsigned int mdlen=0;
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    int ok = ctx && sm3 &&
             EVP_DigestInit_ex(ctx, sm3, NULL)==1 &&
             EVP_DigestUpdate(ctx, line, norm)==1 &&
             EVP_DigestFinal_ex(ctx, md, &mdlen)==1 && mdlen==32;
    if (ctx) EVP_MD_CTX_free(ctx);
    char lhex_now[65]={0}; if (ok) hexdump(md,32,lhex_now,sizeof(lhex_now));
    free(line);
    if (!ok || strcmp(lhex_now, tail.lhash) != 0) return -1;

    /* 同步全局游标到 idx 尾 */
    if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) return -1;

    g_degraded_mode = 0;
    log_msg("EXIT DEGRADED MODE: idx/seal repaired; resume normal operation");

    /* 立刻补写一次 seal（此时已解锁，gmseal_write_seal 会再自检一次） */
    (void)gmseal_write_seal();
    return 0;
}


//11-6
static void on_usr1_recover(int sig)
{
    (void)sig;

    /* 1) 重新开句柄并把 g_lines/g_offset/g_chain 对齐到 idx 尾 */
    if (resync_from_disk_tail(/*force_reopen=*/1) != 0)
        return;

    /* 2) 如果之前在降级，尝试退出（会再次做前缀严审） */
    (void)try_exit_degraded_if_repaired();

    /* 把当前可读尾部吃光（若仍降级则可能吃不到，这没关系） */
    (void)process_all_available_lines();

    /* 3) 不在降级就把当前磁盘上的尾部“吃光”，并立刻重写 seal */
    if (gmseal_write_seal() != 0) {
        log_msg("on_usr1_recover: gmseal_write_seal failed (maybe still degraded)");
    }

    (void)resync_from_disk_tail(/*force_reopen=*/0);
}
//11-6


//11-13
/* 尝试在“同 inode + 文件变小”场景下，软对齐修复 idx 尾部以匹配当前 audit.log
 * 返回：
 *   1 = 已修复（idx 被截到与 log 一致），调用方应写 seal 并继续；不做 tampered 快照
 *   0 = 无需修复或完全匹配（未动）
 *  -1 = 发生错误（IO/解析），由调用方走原有逻辑
 */
static int soft_reconcile_after_crash_shrink(off_t cur_size)
{
    IdxArray arr = {0};
    if (load_idx_entries(IDX_PATH, &arr) != 0) return -1;

    FILE *lf = fopen(LOG_PATH, "rb");
    if (!lf) { free_idx_array(&arr); return -1; }

    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3) { fclose(lf); free_idx_array(&arr); return -1; }

    char *line = NULL; size_t cap = 0;
    size_t k = 0; /* 已确认匹配的条数 */
    for (; k < arr.count; k++) {
        const IdxEntry *e = &arr.entries[k];
        if ((unsigned long long)cur_size <= e->off) break; /* 该条偏移已越界到现存文件尾部之外 */

        if (fseeko(lf, (off_t)e->off, SEEK_SET) != 0) break;
        ssize_t n = getline(&line, &cap, lf);
        if (n <= 0) break;

        size_t norm = normalize_line(line, (size_t)n);
        unsigned char md[32]; unsigned int mdlen = 0;
        EVP_MD_CTX *ctx = EVP_MD_CTX_new();
        int ok = ctx &&
                 EVP_DigestInit_ex(ctx, sm3, NULL)==1 &&
                 EVP_DigestUpdate(ctx, line, norm)==1 &&
                 EVP_DigestFinal_ex(ctx, md, &mdlen)==1 && mdlen==32;
        if (ctx) EVP_MD_CTX_free(ctx);
        if (!ok) break;

        char lhex[65]; hexdump(md, 32, lhex, sizeof(lhex));
        if (strncmp(lhex, e->lhash, 64) != 0) break; /* 内容不匹配，停止 */
    }
    free(line);
    fclose(lf);

    if (k < arr.count) {
        /* idx 比 log 长，裁到 k 条 */
        int rc = truncate_idx_to_i_atomic(IDX_PATH, (k > 0) ? (unsigned long long)k : 0ULL);
        free_idx_array(&arr);
        if (rc != 0) return -1;
        return 1; /* repaired */
    }

    free_idx_array(&arr);
    return 0; /* 完全匹配，无需修复 */
}


static int check_rotation_and_rollover(void)
{
    if (g_log_fd < 0) return open_log_handle();

    struct stat st_now;
    if (stat(LOG_PATH, &st_now) != 0) {
        return 0;
    }

    /* A) inode 未变 —— 可能就地截断 */
    if (st_now.st_dev == g_log_dev && st_now.st_ino == g_log_ino) {
        if ((unsigned long long)st_now.st_size < g_offset) {
            log_msg("SUSPICIOUS truncate-in-place: size %llu -> %llu",
                    (unsigned long long)g_offset, (unsigned long long)st_now.st_size);

            (void)wait_file_stable(LOG_PATH, 5, 200);

            if (has_recent_rotated_copy(5)) {
                /* 新一代（与 B 分支 rename 轮转后的处理一致） */
                if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0) {
                    log_msg("truncate-in-place(copytruncate): truncate new idx failed");
                    return -1;
                }
                memset(g_chain, 0, sizeof(g_chain));
                g_lines  = 0;
                g_offset = 0;
                (void)gmseal_write_seal_for(LOG_PATH, IDX_PATH, SEAL_PATH);

                /* 旧 suspicious-lines 依据你的策略处理：你当前实现是重定位 */
                (void)rebase_tamperlines_to_current_log();

                return 0; /* 不打 tamper 快照 —— 提前结束 A 分支 */
            }

            //11-13------
            int healed = soft_reconcile_after_crash_shrink(st_now.st_size);
            if (healed == 1) {
                /* 重新同步游标并立刻写 seal，视为正常恢复 */
                (void)recover_tail_from_idx(&g_lines, &g_offset, g_chain);
                (void)gmseal_write_seal();
                /* 之前已存在的 suspicious-lines 如有，也可重定位一次 */
                (void)rebase_tamperlines_to_current_log();
                return 0;
            } else if (healed < 0) {
                /* 修复尝试出错，继续走原有 tamper 快照逻辑 */
                log_msg("soft_reconcile_after_crash_shrink failed; fallback to snapshot");
            }
            //11-13------


            /* 去抖：同一 inode + 同一 oldoff，在 10 秒窗口只处理一次（防“风暴”） */
            time_t now_ts = time(NULL);
            if (s_last_trunc_ino == g_log_ino &&
                s_last_trunc_oldoff == g_offset &&
                (now_ts - s_last_trunc_ts) <= 10) {
                return 0; /* 已处理过这一轮截断事件，直接跳过 */
            }
            /* 记录本次事件特征 */
            s_last_trunc_ino    = g_log_ino;
            s_last_trunc_oldoff = g_offset;
            s_last_trunc_ts     = now_ts;


            /* 读取基线 */
            unsigned long long baseline_records = 0;
            char baseline_chain_hex[65] = {0};
            struct seal S0;
            if (parse_seal_file(SEAL_PATH, &S0) == 0) {
                baseline_records = strtoull(S0.records, NULL, 10);
                strncpy(baseline_chain_hex, S0.chain, sizeof(baseline_chain_hex)-1);
                free_seal(&S0);
            } else {
                IdxEntry last0 = {0};
                if (read_last_idx_entry(&last0) == 0) {
                    baseline_records = last0.i;
                    strncpy(baseline_chain_hex, last0.chash, sizeof(baseline_chain_hex)-1);
                }
            }

            time_t ts = time(NULL);
            char tampered_path[PATH_MAX], tampered_idx[PATH_MAX], tampered_seal[PATH_MAX];
            snprintf(tampered_path, sizeof(tampered_path),
                     "%s.tampered-%ld-trunc-ino%ju_oldoff%llu_newsz%llu",
                     LOG_PATH, (long)ts, (uintmax_t)g_log_ino,
                     (unsigned long long)g_offset,
                     (unsigned long long)st_now.st_size);
            snprintf(tampered_idx,  sizeof(tampered_idx),  "%s.idx",  tampered_path);
            snprintf(tampered_seal, sizeof(tampered_seal), "%s.seal", tampered_path);

            int snap_ok = (copy_file_path_to_path(LOG_PATH, tampered_path) == 0);
            if (!snap_ok) log_msg("tamper snapshot (truncate): copy %s -> %s failed", LOG_PATH, tampered_path);

            if (snap_ok) {
                IdxArray arr = {0};
                unsigned long long target_records = baseline_records;
                char target_chain_hex[65] = {0};

                if (load_idx_entries(IDX_PATH, &arr) == 0) {
                    if (arr.count == 0) {
                        /* 在线 idx 为空 -> 快照降级为 records=0，避免后续校验把 1..N 全当 Inserted */
                        target_records = 0;
                        memset(target_chain_hex, '0', 64); target_chain_hex[64] = 0;
                        log_msg("online idx empty when snapshotting; degrade snapshot to records=0");
                    } else {
                        if (target_records == 0 || target_records > arr.count)
                            target_records = arr.count;
                        strncpy(target_chain_hex,
                                arr.entries[target_records - 1].chash,
                                sizeof(target_chain_hex) - 1);
                    }
                } else {
                    /* 连 idx 都读不了：还是降级 */
                    target_records = 0;
                    memset(target_chain_hex, '0', 64); target_chain_hex[64] = 0;
                    log_msg("cannot read online idx; degrade snapshot to records=0");
                }

                /* 重建快照 idx（target_records==0 时会生成空文件） */
                if (build_idx_for_snapshot(IDX_PATH, tampered_path, tampered_idx,target_records) == 0)
                {
                    if (gmseal_write_seal_custom(tampered_path, tampered_idx, tampered_seal,
                                                 target_records,
                                                 target_chain_hex[0] ? target_chain_hex : NULL) == 0) {
                        log_msg("tamper snapshot(TRUNC) OK: %s (seal.records=%llu)",
                                tampered_path, (unsigned long long)target_records);
                    } else {
                        log_msg("tamper snapshot(TRUNC): write seal failed");
                    }
                } else {
                    log_msg("tamper snapshot(TRUNC): build idx failed");
                }
                free_idx_array(&arr);
            }

            /* 新一代 */
            if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0) {
                log_msg("truncate-in-place: truncate new idx failed");
                return -1;
            }
            memset(g_chain, 0, sizeof(g_chain));
            g_lines  = 0;
            g_offset = 0;
            (void)gmseal_write_seal_for(LOG_PATH, IDX_PATH, SEAL_PATH);

            /* 截断后的 tamperlines 重定位到当前 audit.log */
            (void)rebase_tamperlines_to_current_log();

        }
        return 0;
    }

    /* B) inode 变化 —— 先把旧 FD 吃到 EOF */
    log_msg("detected inode change: old ino=%ju -> new ino=%ju",
            (uintmax_t)g_log_ino, (uintmax_t)st_now.st_ino);

    dev_t prev_dev = g_log_dev; //11-8
    ino_t prev_ino = g_log_ino; //11-8
    (void)drain_old_file_to_eof();

    /* 正常轮转？ */
    char rotated_path[PATH_MAX] = {0};
    if (find_rotated_name_by_inode(rotated_path, g_log_dev, g_log_ino) == 0) {
        log_msg("rotation(rename) confirmed: archived file = %s", rotated_path);

        close_log_handle();
        if (open_log_handle() != 0) { log_msg("rotation(rename): open new audit.log failed"); return -1; }
        if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0) { log_msg("rotation(rename): truncate new idx failed"); return -1; }

        memset(g_chain, 0, sizeof(g_chain));
        g_lines  = 0;
        g_offset = 0;
        (void)gmseal_write_seal_for(LOG_PATH, IDX_PATH, SEAL_PATH);

        // /* 正常轮转：归档并清空 tamperlines（新一代从零开始） */
        // {
        //     const char *TLP = "/var/log/audit/suspicious-lines";
        //     char bak[PATH_MAX];
        //     time_t ts = time(NULL);
        //     snprintf(bak, sizeof(bak), "%s.rotated-%ld-ino%ju", TLP, (long)ts, (uintmax_t)prev_ino);
        //     (void)rename(TLP, bak); /* 不存在也无妨 */
        //     int zfd = open(TLP, O_WRONLY|O_CREAT|O_TRUNC, 0600);
        //     if (zfd >= 0) { fsync(zfd); close(zfd); }
        // }

        /* 正常轮转：仅当 suspicious-lines 非空时才归档；不要重建空文件 */
        {
            const char *TLP = "/var/log/audit/suspicious-lines";
            struct stat st_sl;
            if (stat(TLP, &st_sl) == 0) {
                if (st_sl.st_size > 0) {
                    char bak[PATH_MAX];
                    time_t ts = time(NULL);
                    snprintf(bak, sizeof(bak), "%s.rotated-%ld-ino%ju", TLP, (long)ts, (uintmax_t)prev_ino);
                    (void)rename(TLP, bak);   /* 只归档非空文件 */
                } else {
                    (void)unlink(TLP);         /* 空文件直接删掉，保持“无则无” */
                }
            }
            /* 不要创建新的空 suspicious-lines；首次出现可疑行时再由 O_CREAT 创建 */
        }

        return 0;
    }

    /* C) inode 改变但找不到旧文件 —— 可疑重写 */
    log_msg("inode changed but no archived filename found -> suspicious rewrite");
    (void)wait_file_stable(LOG_PATH, 5, 200);

    unsigned long long baseline_records = 0;
    char baseline_chain_hex[65] = {0};
    struct seal S0;
    if (parse_seal_file(SEAL_PATH, &S0) == 0) {
        baseline_records = strtoull(S0.records, NULL, 10);
        strncpy(baseline_chain_hex, S0.chain, sizeof(baseline_chain_hex)-1);
        free_seal(&S0);
    } else {
        IdxEntry last0 = {0};
        if (read_last_idx_entry(&last0) == 0) {
            baseline_records = last0.i;
            strncpy(baseline_chain_hex, last0.chash, sizeof(baseline_chain_hex)-1);
        }
    }

    time_t ts = time(NULL);
    char tampered_path[PATH_MAX], tampered_idx[PATH_MAX], tampered_seal[PATH_MAX];
    snprintf(tampered_path, sizeof(tampered_path),
             "%s.tampered-%ld-old%ju_new%ju", LOG_PATH, (long)ts,
             (uintmax_t)g_log_ino, (uintmax_t)st_now.st_ino);
    snprintf(tampered_idx,  sizeof(tampered_idx),  "%s.idx",  tampered_path);
    snprintf(tampered_seal, sizeof(tampered_seal), "%s.seal", tampered_path);

    int snap_ok = (copy_file_path_to_path(LOG_PATH, tampered_path) == 0);
    if (!snap_ok) log_msg("tamper snapshot: copy %s -> %s failed", LOG_PATH, tampered_path);

    if (snap_ok) {
        IdxArray arr = {0};
        unsigned long long target_records = baseline_records;
        char target_chain_hex[65] = {0};

        if (load_idx_entries(IDX_PATH, &arr) == 0) {
            if (arr.count == 0) {
                target_records = 0;
                memset(target_chain_hex, '0', 64); target_chain_hex[64] = 0;
                log_msg("online idx empty when snapshotting; degrade snapshot to records=0");
            } else {
                if (target_records == 0 || target_records > arr.count)
                    target_records = arr.count;
                strncpy(target_chain_hex,
                        arr.entries[target_records - 1].chash,
                        sizeof(target_chain_hex) - 1);
            }
        } else {
            target_records = 0;
            memset(target_chain_hex, '0', 64); target_chain_hex[64] = 0;
            log_msg("cannot read online idx; degrade snapshot to records=0");
        }

        if (build_idx_for_snapshot(IDX_PATH, tampered_path, tampered_idx,target_records) == 0)
        {
            if (gmseal_write_seal_custom(tampered_path, tampered_idx, tampered_seal,
                                         target_records,
                                         target_chain_hex[0] ? target_chain_hex : NULL) == 0) {
                log_msg("tamper snapshot OK: %s (seal.records=%llu)",
                        tampered_path, (unsigned long long)target_records);
            } else {
                log_msg("tamper snapshot: write seal failed");
            }
        } else {
            log_msg("tamper snapshot: build idx failed");
        }
        free_idx_array(&arr);
    }

    /* 切到新一代 */
    close_log_handle();
    (void)open_log_handle();
    if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0) {
        log_msg("suspicious rewrite: truncate new idx failed");
        return -1;
    }
    memset(g_chain, 0, sizeof(g_chain));
    g_lines  = 0;
    g_offset = 0;
    (void)gmseal_write_seal_for(LOG_PATH, IDX_PATH, SEAL_PATH);

    /* 可疑重写后：把 tamperlines 的行号/偏移重定位到当前 audit.log */
    (void)rebase_tamperlines_to_current_log();

    log_msg("suspicious rewrite handled: snapshot written; new generation started");
    return 0;
}

/* 初始化 */
static int gmseal_init(void)
{
    memset(g_chain,0,sizeof(g_chain));
    g_lines = 0; g_offset = 0; g_degraded_mode = 0;

    (void)open_idx_handle();
    (void)open_log_handle();

    int vs = 0;
    if (access(SEAL_PATH, R_OK) != 0) {
        log_msg("no existing seal: treat as first-run");
        vs = 1;
    } else {
        vs = 0;
    }

    if (vs > 0) {
        unsigned long long idx_lines = idx_count_lines();
        if (idx_lines == 0) {
            unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
            if (rebuild_idx_from_log_atomic(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) != 0) {
                log_msg("rebuild_idx_from_log_atomic failed (first run)"); return -1;
            }
            if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) {
                log_msg("recover_tail_from_idx failed after first-run rebuild"); return -1;
            }
            log_msg("first-run: lines=%llu off=%llu", (unsigned long long)g_lines, (unsigned long long)g_offset);
        } else {
            if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) {
                log_msg("recover_tail_from_idx failed (existing idx)"); return -1;
            }
            log_msg("resume from existing idx: lines=%llu off=%llu", (unsigned long long)g_lines, (unsigned long long)g_offset);
        }
    } else {
        if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) {
            log_msg("recover_tail_from_idx failed"); return -1;
        }
        log_msg("verified or degraded: lines=%llu off=%llu (no rebuild)", (unsigned long long)g_lines, (unsigned long long)g_offset);
    }

    (void)check_rotation_and_rollover();
    return 0;
}

/* stdin 驱动：audispd 给我们喂数据（用作“时钟”触发） */
static int stdin_event_loop(void)
{
    char *line = NULL; size_t cap = 0; ssize_t n;
    while (g_run && (n = getline(&line, &cap, stdin)) != -1) {
        (void)n;

        //11-6
        if (g_degraded_mode) {
            (void)check_rotation_and_rollover();
            (void)try_exit_degraded_if_repaired();
            if (g_degraded_mode)          /* 仍未恢复，直接下一轮 */
                continue;
        }

        (void)check_rotation_and_rollover();

        int rc = process_all_available_lines();
        if (rc < 0) {
            trigger_alarm("gmseal plugin processing error - exiting");
            free(line); return -1;
        }
        if (!g_degraded_mode && ALWAYS_REFRESH_SEAL && rc > 0) {
            if (gmseal_write_seal() != 0) log_msg("gmseal_write_seal failed");
        }
    }
    if (line) free(line);
    return 0;
}

/* main */
int main(int argc, char **argv)
{
    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();

    log_open();
    log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

    if (acquire_singleton_lock() != 0) {
        if (g_log) fclose(g_log);
        EVP_cleanup();
        ERR_free_strings();
        return 0;
    }

    if (gm_crypto_init() != 0) {
        log_msg("gm_crypto_init failed");
        fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
        release_singleton_lock();
        if (g_log) fclose(g_log);
        EVP_cleanup();
        ERR_free_strings();
        return 2;
    }

    signal(SIGINT, on_signal);
    signal(SIGTERM, on_signal);
    signal(SIGUSR1, on_usr1_recover); //11-6

    if (gmseal_init() != 0) {
        log_msg("gmseal_init failed (abort)");
        gm_crypto_cleanup();
        release_singleton_lock();
        if (g_log) fclose(g_log);
        EVP_cleanup();
        ERR_free_strings();
        return 2;
    }

    /* 启动后先把当前可读尾部吃完，再写首次 seal（仅预检，不对齐） */
    {
        int ate = process_all_available_lines();
        if (ate < 0) {
            log_msg("initial tail scan failed");
        }
    }
    if (!g_degraded_mode) {
        if (gmseal_write_seal() != 0) {
            log_msg("initial seal write failed");
        }
    } else {
        log_msg("initial: degraded mode active, skip initial seal");
    }

    if (stdin_event_loop() != 0) {
        log_msg("stdin_event_loop aborted due to errors");
    }

    (void)gmseal_write_seal(); /* 降级态会跳过 */
    (void)check_rotation_and_rollover();

    close_idx_handle();
    close_log_handle();
    gm_crypto_cleanup();
    release_singleton_lock();
    if (g_log) fclose(g_log);
    EVP_cleanup();
    ERR_free_strings();
    log_msg("audisp-gmseal exited");
    return 0;
}